【Vue】Re06 组件化
将一个应用页面拆分成若干个可重复使用的组件
一、Vue的组件的使用步骤:
1、创建组件构造器
2、注册组件
3、使用组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <!-- 3、使用组件 --> <component-instance></component-instance> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--<script src="./../dependencies/vue.js"></script>--> <script type="text/javascript"> /* 1、创建一个组件实例 创建需要注入一个JS对象,其中template属性是需要的html模板代码 */ const componentInstance = Vue.extend({ template : '<div><h3>This is a template instance</h3><p>template content</p></div>' }); /* 2、将组件注册进Vue中 参数1是组件的标签名称,参数2是组件的实例对象 */ Vue.component('component-instance', componentInstance); const v = new Vue({ el : '#v' }); </script> </body> </html>
一些简写的使用:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <!-- 对模板不需要插入内容可以直接使用自闭和标签 --> <component-instance/> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--<script src="./../dependencies/vue.js"></script>--> <script type="text/javascript"> const componentInstance = Vue.extend({ template : /* 可以使用特殊符号``创建模板域编写模板 */ ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` }); Vue.component('component-instance', componentInstance); const v = new Vue({ el : '#v' }); </script> </body> </html>
二、全局组件和私有组件:
上述使用的就是全局组件,该组件不需要被Vue实例注册,任何Vue实例都可以使用该组件
/* 创建 */ const componentInstance = Vue.extend({ template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` }); /* 注册 */ Vue.component('component-instance', componentInstance);
私有组件,注册就不是给Vue注册,而是注册到具体的Vue实例中
/* 创建 */ const componentInstance = Vue.extend({ template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` }); /* 注册 */ const vm = new Vue({ el : '#v', components : { componentInstance : componentInstance } });
三、父子组件:
创建子组件和父组件 -> 子组件可以注册到父组件 -> 父组件再注册到Vue实例
Vue实例相当于同时把父子组件都注册进来了,调用父组件就相当于调用子组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <parent-comp></parent-comp> </div> <!--<script src="./../dependencies/vue-min.js"></script>--> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> let childComp = Vue.extend({ template : ` <div> <h1>This is a Sub Component</h1> </div> ` }); let parentComp = Vue.extend({ template : <!-- 可以在这里嵌套使用组件 --> ` <div> <h1>This is a Parent Component</h1> <child-comp></child-comp> </div> `, components : { /* 在父组件中也需要把组件注册进来 */ childComp } }); let vm = new Vue({ el : '#app', components : { /* 再把父组件注册到这个Vue实例中 */ parentComp } }); </script> </body> </html>
关于父子组件的一些问题:
如果单独使用子组件在Vue实例中,私有组件仍然需要在Vue实例中注册
反之全局组件则不需要注册就可以使用
注册全局组件的语法糖:
全局组件注册
/* 2.0.x 原版 */ const componentInstance = Vue.extend({ template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` }); Vue.component('component-instance', componentInstance); // ------------------------------------------------------ /* 3.0.x 方式 */ Vue.component('component-instance', { template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` })
私有组件注册:
/* 2.0.x 原版 */ const componentInstance = Vue.extend({ template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` }); const v1 = new Vue({ el : '#v', components : { componentInstance : componentInstance // 使用同名可以直接简写一个 componentInstance } }); // ------------------------------------------------------------------------------------- /* 3.0.x 方式 */ const v2 = new Vue({ el : '#v', components : { componentInstance : { template : ` <div> <h3>This is a template instance</h3> <p>template content</p> </div> ` } } });
四、模板剥离解耦:
模板的HTML代码应该独立于组件存在,为了提升可读性和解耦合
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <cpn></cpn> <cpn2></cpn2> </div> <!-- 写法一 --> <script type="text/x-template" id="cpn"> <div> <h3>这是模板1</h3> <p>这是内容</p> </div> </script> <!-- 写法二 --> <template id="cpn2"> <div> <h3>这是模板2</h3> <p>这是内容</p> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--<script type="text/javascript" src="../dependencies/vue.js"></script>--> <script type="text/javascript"> /* 把组件注册 */ const cpn = Vue.component('cpn', { template: '#cpn' }); const cpn2 = Vue.component('cpn2', { template: '#cpn2' }); const v = new Vue({ el : '#v', }); </script> </body> </html>
五、组件与Data函数
解决每个组件自己的属性,数据独立性问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <cpn></cpn> <btn-test></btn-test> <!-- 保证数据的独立性 --> <btn-test></btn-test> <btn-test></btn-test> <btn-test></btn-test> <!-- 如果要共享直接写data属性中而不是函数 --> <hr> <btn-test2></btn-test2> </div> <template id="cpn"> <div> <h3>这是组件</h3> <p> 这是内容 {{txt}} </p> </div> </template> <!-- 为什么组件必须使用函数传递数据? --> <!-- 因为组件可以被允许在一个Vue实例中多次出现 --> <!-- 组件自身所携带的属性数据是否为公用还是独立成一个问题,为了保证数据独立,Vue采取了这样的data函数方式解决 --> <template id="btn-test"> <div> <p> <button @click="decrement()"> - </button> {{counter}} <button @click="increment()"> + </button> </p> </div> </template> <template id="btn-test2"> <div> <p> <button @click="decrement()"> - </button> {{counter}} <button @click="increment()"> + </button> </p> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--<script type="text/javascript" src="../dependencies/vue.js"></script>--> <script type="text/javascript"> /* 模板具有自己的HTML模板,且属性数据也是独立于Vue实例存在的 */ const cpn = Vue.component('cpn', { template : '#cpn', /* Vue实例使用data对象,但是组件则使用data函数进行处理 */ data () { return { /* 该函数要求必须返回类型为一个对象 */ txt : 'aaa' } } }); const btnTest = Vue.component('btn-test', { template: '#btn-test', data () { return { counter : 0 } }, methods : { increment () { this.counter ++ }, decrement () { this.counter -- } } }) const btnTest2 = Vue.component('btn-test2', { template: '#btn-test2', data : { counter : 0 }, methods : { increment () { data.counter.counter ++ }, decrement () { data.counter.counter -- } } }) const v = new Vue({ el : '#v', }); </script> </body> </html>
六、父子组件通信问题:
父组件 给 子组件传递消息 使用Props属性实现
子组件 给 父组件传递消息 使用$emit对象发送事件
Props属性传递数据:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <!-- 2、在父组件调用的时候 声明指令进行绑定 --> <component-instance v-bind:comp-message="message" v-bind:comp-movie-list="movieList" ></component-instance> </div> <template id="componentInstance"> <div> <h3>这是子组件 {{compMessage}}</h3> <!-- 3、使用mustache语法引用 --> <p>子组件内容</p> <ul> <li v-for="movie in compMovieList">{{movie}}</li> </ul> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!--<script type="text/javascript" src="../dependencies/vue.js"></script>--> <script type="text/javascript"> const componentInstance = Vue.extend({ template : '#componentInstance', props : ['compMessage', 'compMovieList'], // 1、首先声明 props,数组里面要存放的是的组件的属性变量名称 }) const v = new Vue({ el : '#v', data : { /* 父组件的消息数据 */ message : 'hello', movieList : [ '海王', '海贼王', '疾风传', 'eva' ] }, components : { componentInstance : componentInstance } }); </script> </body> </html>
关于Props的几种写法:
1、原始数组写法:
props : ['property1', 'property2', 'property3', ...]
2、对象写法:
属性是变量名称,值是变量类型:
props : {
property1 : dataType1,
property2 : dataType2,
property3 : dataType3,
},
类型除了基本类型之外,还可以是自定义类型
String 字符
Number 数字
Boolean 布尔
Array 数组
Object 对象原型
Date 日期
Function 函数
Symbol
自定义类?
3、变量对象写法:
props : { ccc : { // 可以再对该属性继续细分 type : Array, // 单独设置类型限制 // default : [ // 设置默认值,在vue2.5.x版本以下可以这样写 // '111', // '111', // '111', // ], default() { // 建议使用函数进行返回 return [ '111', '111', '111', ]; }, required : true // 使用此组件必须传递此属性项 }, bBb : { type : String, default() { return 'null'; } } },
关于驼峰标识问题:
在组件使用时v-bind指令必须按照减号分割线处理,指令目前不支持驼峰命名
<div id="v"> <!-- 2、在父组件调用的时候 声明指令进行绑定 --> <component-instance v-bind:comp-message="message" v-bind:comp-movie-list="movieList" ></component-instance> </div>
子组件传递信息给父组件 $emit:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <!-- 2、在使用时将事件传递到父组件中, 而子组件使用$emit定义的自定义事件绑定父组件的函数 --> <cpn-ins v-on:cst="emitTaker"></cpn-ins> </div> <template id="componentInstance"> <div> <!-- 通过遍历的每一个c元素获取对象 --> <button v-for="c in categories" @click="emitEventSenderTest(c)">{{c.name}}</button> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const componentInstance = Vue.extend({ template : '#componentInstance', data () { return { categories : [ {id : 'aaa', name : '分类1'}, {id : 'bbb', name : '分类2'}, {id : 'ccc', name : '分类3'}, {id : 'ddd', name : '分类4'}, {id : 'eee', name : '分类5'}, ] } }, methods : { /* 可以把当前对象用参数注入到事件的函数中来 */ emitEventSenderTest (thisObject) { console.log('子组件发送 -> ' + thisObject); /* 1、通过组件的$emit对象发送事件给父组件 注意接受的事件名称定义使用小写加杠 */ this.$emit('cst', thisObject); // 参数1自定义事件, 参数2传递当前对象 } } }); const v = new Vue({ el : '#v', methods : { /* 3、定义监听接受函数 */ emitTaker(thisObject) { console.log('父组件接收 -> ' + thisObject); } }, components : { cpnIns : componentInstance }, }) </script> </body> </html>
父子组件的双向绑定:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <h3>实例变量 {{n1}} {{n2}}</h3> <cpn v-bind:num1="n1" v-bind:num2="n2" /> </div> <template id="sss"> <div> <h3>这是组件 {{num1}} {{num2}}</h3> <p> 这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 --> num1 绑定 <input type="text" v-model="num1"> <br> num2 绑定 <input type="text" v-model="num2"> </p> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const vm = new Vue({ el : '#v', data : { n1 : 100, n2 : 50 }, components : { cpn : { template : '#sss', props : { num1 : Number, num2 : Number } } } }) </script> </body> </html>
结果查看:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <h3>Vue实例{{n1}} {{n2}}</h3> <cpn v-bind:num1="n1" v-bind:num2="n2" v-on:num1get="getNum1" v-on:num2get="getNum2" /> </div> <template id="sss"> <div> <h3>这是组件 {{num1}} {{num2}}</h3> <p> 这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 --> <!-- num1 绑定 <input type="text" v-model="num1"> <br>--> <!-- num2 绑定 <input type="text" v-model="num2">--> <!-- 解决方案是不使用v-model,拆分绑定 --> <!-- num1 绑定 <input type="text" v-bind:value="dNum1" @input="dNum1 = $event.target.value"> <br>--> <!-- num2 绑定 <input type="text" v-bind:value="dNum2" @input="dNum2 = $event.target.value">--> <!-- 将方法封装成函数 --> <h3>props:{{num1}}</h3> <h3>data():{{dNum1}}</h3> num1 绑定 <input type="text" v-bind:num1="dNum1" @input="num1Input"> <br> <h3>props:{{num2}}</h3> <h3>data():{{dNum2}}</h3> num2 绑定 <input type="text" v-bind:num1="dNum2" @input="num2Input"> </p> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const vm = new Vue({ el : '#v', data : { n1 : 100, n2 : 50 }, methods : { getNum1(val) { // 父组件则处理接受 // 传入的过程类型发生了转变 console.log(typeof val); 发现确实是String this.n1 = parseInt(val); // 强转了参数处理 }, getNum2(val) { this.n2 = parseInt(val); }, }, computed : { }, components : { cpn : { template : '#sss', props : { num1 : Number, num2 : Number }, data () { return { dNum1 : this.num1, dNum2 : this.num2 } }, methods : { num1Input(eventObject) { this.dNum1 = eventObject.target.value; this.$emit('num1get', this.dNum1); // 利用外值传递结合emit事件发送出去 }, num2Input(eventObject) { this.dNum2 = eventObject.target.value; this.$emit('num2get', this.dNum2); }, }, } } }) </script> </body> </html>
使用watch实现双向绑定:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <h3>Vue实例{{n1}} {{n2}}</h3> <cpn v-bind:num1="n1" v-bind:num2="n2" v-on:num1get="getNum1" v-on:num2get="getNum2" /> </div> <template id="sss"> <div> <h3>这是组件 {{num1}} {{num2}}</h3> <p> 这是组件内容 <br> <!-- 当前这样的绑定对子组件有效,但是对父组件的变量绑定是无效的 --> <!-- num1 绑定 <input type="text" v-model="num1"> <br>--> <!-- num2 绑定 <input type="text" v-model="num2">--> <!-- 解决方案是不使用v-model,拆分绑定 --> <!-- num1 绑定 <input type="text" v-bind:value="dNum1" @input="dNum1 = $event.target.value"> <br>--> <!-- num2 绑定 <input type="text" v-bind:value="dNum2" @input="dNum2 = $event.target.value">--> <!-- 将方法封装成函数 --> <h3>props:{{num1}}</h3> <h3>data():{{dNum1}}</h3> num1 绑定 <input type="text" v-model="dNum1"> <br> <!-- 这里的model绑定的可能是watch里面的函数 --> <h3>props:{{num2}}</h3> <h3>data():{{dNum2}}</h3> num2 绑定 <input type="text" v-model="dNum2"> </p> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const vm = new Vue({ el : '#v', data : { n1 : 100, n2 : 50 }, methods : { getNum1(val) { // 父组件则处理接受 // 传入的过程类型发生了转变 console.log(typeof val); 发现确实是String this.n1 = parseInt(val); // 强转了参数处理 }, getNum2(val) { this.n2 = parseInt(val); }, }, computed : { }, components : { cpn : { template : '#sss', props : { num1 : Number, num2 : Number }, data () { return { dNum1 : this.num1, dNum2 : this.num2 } }, watch : { /* 这里函数必须命名为返回变量一致 */ dNum1(newVal, oldVal) { /* 参数是一个新的值和一个旧值 */ this.$emit('num1get', newVal); // 利用外值传递结合emit事件发送出去 }, dNum2(newVal, oldVal) { this.$emit('num2get', newVal); }, } } } }) </script> </body> </html>
父组件访问子组件:引用方式
Vue还提供了两个封装对象:
$children
$refs
$children演示案例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <cpn></cpn> <cpn></cpn> <cpn></cpn> <cpn></cpn> <button @click="showChildComponent">查看子组件数组对象</button> </div> <template id="sss"> <div> <h3>我是子组件</h3> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const vm = new Vue({ el : '#v', data : { message : 'Hello' }, methods : { showChildComponent() { // console.log(this.$children); for (let $child of this.$children) { // 遍历可以得到每一个子组件 $child.logMessage(); console.log($child.name); } } }, components : { cpn : { template : '#sss', data() { return { name : '子组件name' } }, methods: { logMessage() { console.log('sss'); } } } } }) </script> </body> </html>
$refs获取具体子组件实例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="v"> <cpn></cpn> <cpn></cpn> <cpn ref="aaa"></cpn> <!-- 使用$ref需要先声明标注 --> <cpn></cpn> <button @click="showChildComponent">查看子组件数组对象</button> </div> <template id="sss"> <div> <h3>我是子组件</h3> </div> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> const vm = new Vue({ el : '#v', data : { message : 'Hello' }, methods : { showChildComponent() { // 使用时引用具体值 const aaa = this.$refs.aaa; console.log(aaa.name); aaa.logMessage(); } }, components : { cpn : { template : '#sss', data() { return { name : '子组件name' } }, methods: { logMessage() { console.log('sss'); } } } } }) </script> </body> </html>
子组件访问父组件和根组件也提供了一个对象:
this.$parent & this.$root
这里就不再赘述了