深入理解 Vue 组件
组件使用中的细节点
使用 is 属性,解决组件使用中的bug问题
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <title>组件使用中的细节点</title> 9 <script src="./vue.js"></script> 10 </head> 11 12 <body> 13 <div id="root"> 14 <table> 15 <tbody> 16 <!-- H5编码规范要求,tbody内必须是tr,因此row组件不能用,会产生bug, 17 因此 is 关键字起到了很好的作用,将此时的 tr 标签等于我们创建的 row 子组件。 18 完美解决了既要使用组件永不会影响H5编码规范的问题 19 不仅仅是table标签,ul ol select 标签都有相同的问题。--> 20 <tr is="row"></tr> 21 <tr is="row"></tr> 22 <tr is="row"></tr> 23 </tbody> 24 </table> 25 </div> 26 27 <script> 28 // 创建全局子组件 29 Vue.component('row',{ 30 template:"<tr><td>this is a row</td></tr>" 31 }) 32 33 var vm = new Vue({ 34 el:"#root", 35 36 }) 37 </script> 38 </body> 39 40 </html>
子组件定义data数据,data必须是个函数
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <title>Document</title> 9 <script src="./vue.js"></script> 10 </head> 11 12 <body> 13 <div id="root"> 14 <table> 15 <tbody> 16 <tr is="row"></tr> 17 <tr is="row"></tr> 18 <tr is="row"></tr> 19 </tbody> 20 </table> 21 </div> 22 <script> 23 // 子组件 24 Vue.component("row", { 25 // 子组件定义数据data的方法必须是一个函数返回,不能像根对象一样 26 data: function () { 27 return { 28 content: 'this is a row' 29 } 30 }, 31 template: '<tr><td>{{content}}</td></tr>' 32 }) 33 34 var vm = new Vue({ 35 el: "#root", 36 }) 37 </script> 38 </body> 39 40 </html>
Vue中的 ref 引用的内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>ref</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- 在vue当中,可以通过ref获取dom节点 --> <div ref='hello' @click="handleClick">hello world</div> </div> <script> var vm = new Vue({ el: "#root", methods: { handleClick: function () { // 获取dom中的内容 // this.$refs.hello 获取ref=hello的dom节点 alert(this.$refs.hello.innerHTML) } } }) </script> </body> </html>
vue实现计数器功能
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>计数器功能</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <counter ref='one' @change="handleChange"></counter> <counter ref='two' @change="handleChange"></counter> <!-- 求和 --> <div>{{total}}</div> </div> <script> // 子组件 Vue.component('counter', { template: '<div @click="handleClick">{{number}}</div>', data: function () { return { number: 0 } }, methods: { handleClick: function () { this.number++ // 向外发送change事件 this.$emit('change') } } }) var vm = new Vue({ el: "#root", data: { total: 0 }, methods: { handleChange: function () { this.total = this.$refs.one.number + this.$refs.two.number // console.log(this.$refs.one.number) // console.log(this.$refs.two.number) } } }) </script> </body> </html>
父子组件间传值
父组件向子组件传递数据
- 父组件通过属性的形式向子组件传递数据。
- 父组件可以随意的向子组件传递参数。
- 但是子组件绝对不能去修改父组件传进来的参数(单向数据流)。
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <title>父子间组件传值</title> 9 <script src="./vue.js"></script> 10 </head> 11 12 <body> 13 <div id="root"> 14 <!-- 父组件都是通过属性的形式向子组件传递数据 --> 15 <counter :count="1"></counter> 16 <counter :count="2"></counter> 17 </div> 18 19 <script> 20 21 // 局部组件 22 var counter = { 23 // props 表示子组件接受父组件的内容 24 props: ['count'], 25 data: function () { 26 return { 27 // 子组件自己的data number值 28 number:this.count 29 } 30 }, 31 template: "<div @click='handleClick'>{{number}}</div>", 32 methods: { 33 // 点击累加方法 34 handleClick: function () { 35 // 父组件可以随意的向子组件传递参数 36 // 但是子组件绝对不能去修改父组件传进来的参数 单向数据流 37 // 因此修改自己的Number值 38 this.number++ 39 }, 40 } 41 } 42 43 var vm = new Vue({ 44 el: "#root", 45 // 注册局部组件. 46 components: { 47 counter: counter, 48 } 49 }) 50 </script> 51 52 </body> 53 54 </html>
子组件向父组件传值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>父子间组件传值</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- 父组件都是通过属性的形式向子组件传递数据 --> <counter :count="3" @change="handleChange"></counter> <counter :count="2" @change="handleChange"></counter> <div>{{total}}</div> </div> <script> // 局部组件 var counter = { // props 表示子组件接受父组件的内容 props: ['count'], data: function () { return { // 子组件自己的data number值 number:this.count } }, template: "<div @click='handleClick'>{{number}}</div>", methods: { // 点击累加方法 handleClick: function () { // 父组件可以随意的向子组件传递参数 // 但是子组件绝对不能去修改父组件传进来的参数 单向数据流 // 因此修改自己的Number值 this.number++ // 向外触发事件,后可以跟参数 this.$emit('change',1) }, } } var vm = new Vue({ el: "#root", data:{ total:5, }, // 注册局部组件. components: { counter: counter, }, methods:{ handleChange:function(step){ // step = 1 步长为2 // 求和等于默认值+点击一下的步长 this.total += step } } }) </script> </body> </html>
组件参数校验与非props特性
组件参数校验
组件参数校验是指:父组件向子组件传递参数的时候,子组件有权向父组件提出参数的形式和要求,并检验父组件传进的参数是否合乎要求。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>组件参数校验与非props特性</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- <child :content="123"></child> --> <child content="123"></child> </div> <script> Vue.component('child',{ props:{ // content:String, // 子组件接收到的content数据,必须是一个字符串类型 // content:[Number,String] // 子组件接收到的content数据,要么是字符串,要么是数字 content:{ // 接收content type:String, //类型type必须是string // required:true, // 表示content必需传 // default:'default value', // 如果没有传进来,默认显示这个 validator:function(value){ // 校验器校验传入的内容长度必须大于5 return (value.length>5) }, } }, template:'<div>{{content}}</div>', }) var vm = new Vue({ el:"#root", }) </script> </body> </html>
非 Props 特性
Props 特性是指:当你的父组件使用子组件的时候通过属性向子组件传值的时候,恰好子组件里面声明了对父组件传递过来的属性的接收。
非Props 特性是指:父组件向子组件传递了一个属性,但是子组件并没有props接收的内容,也就是说,子组件并没有声明要接受父组件传递进来的属性。
非Props 特性特点一:如果子组件没人接收父组件传进的属性,则子组件不能使用父组件传进的值。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>非 Props 特性</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- <child :content="123"></child> --> <child content="hell"></child> </div> <script> Vue.component('child', { // props: { // content: { // 接收content // type: String, //类型type必须是string // } // }, // content 找不到,就会报错 template: '<div>{{content}}</div>', }) var vm = new Vue({ el: "#root", }) </script> </body> </html>
非Props 特性特点二:DOM中会保留父组件传递给子组件的属性标识
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>非 Props 特性</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- <child :content="123"></child> --> <child content="hell"></child> </div> <script> Vue.component('child', { // props: { // content: { // 接收content // type: String, //类型type必须是string // } // }, template: '<div>hello</div>', // content 找不到,就会报错 // template: '<div>{{content}}</div>', }) var vm = new Vue({ el: "#root", }) </script> </body> </html>
给组件绑定原生事件
很简单,在绑定事件的click后面加一个修饰符就行。
修饰符为 .native
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>给组件绑定原生事件</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- 原生点击事件 --> <child @click.native="handleClick"></child> </div> <script> Vue.component('child',{ template:'<div @click="handleChildClick">Child</div>', }) var vm = new Vue({ el:"#root", methods:{ handleClick:function(){ alert('click') } } }) </script> </body> </html>
非父子组件间的传值
情景分析
我们可以把一个网页拆分成很多个部分,每个部分就是我们代码中是我一个组件,如下面的一张图:
如果 1 2 层需要进行传值,则为父子组件之间的传值,通信方式在之前的内容讲到过。
如果 1 3 层进行传值,则为非父子组件间的传值,应该怎么办呢?
第一中方式:和父子组件间传值一样,一层一层的传递,第一层传给第二层,第二层在传给第三层,反之亦然。但是这种传值方式显然不方便太繁琐。
加入 3 3 层进行的非父子组件传值,又会是怎样的处理方法呢?
这种情况显然更加不适合层层传值,即第三层传给第二层,第二层传给第一层,第一层传给第二层,第二层传给第三层,累死了!代码变得非常的复杂。
非父子组件传值解决方法
第一种方法,我们可以使用 VUE 官方提供的一个数据层的框架,名字叫做 VUEX 来解决,但是使用有难度。
第二种方法,使用 发布订阅模式 来解决非父子组件的传值问题,在vue中叫做 总线机制 。
使用总线机制解决非父子组件传值问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>非父子组件间的传值(Bus|总线|发布订阅模式|观察者模式)</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <child content="Jayvee"></child> <child content="Wong"></child> </div> <script> Vue.prototype.bus = new Vue() Vue.component('child',{ data:function(){ return{ selfContent:this.content } }, template:'<div @click="handleClick">{{selfContent}}</div>', props:{ content:String, }, methods:{ handleClick:function(){ this.bus.$emit('change',this.selfContent) } }, mounted:function(){ var this_ = this this.bus.$on('change',function(msg){ this_.selfContent = msg }) } }) var vm =new Vue({ el:"#root", }) </script> </body> </html>
VUE 中的插槽 - slot
父组件通过传值的方式向子组件添加标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue中的插槽(slot)</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <child content="<p>wjw</p>"></child> </div> <script> Vue.component('child',{ props:['content'], template:'<div><p>hello</p><div v-html="this.content"></div></div>' }) var vm = new Vue({ el:"#root", }) </script> </body> </html>
使用插槽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue中的插槽(slot)</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <child> <p>wjw</p> </child> </div> <script> Vue.component('child',{ template:'<div><p>hello</p><slot>默认内容</slot></div>' }) var vm = new Vue({ el:"#root", }) </script> </body> </html>
传入header和footer
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue中的插槽(slot)</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <body-content> <div slot='header' class="header">header</div> <div slot='footer' class="footer">footer</div> </body-content> </div> <script> Vue.component('body-content',{ template:`<div> <slot name='header'></slot> <div class="content">content</div> <slot name='footer'></slot> </div>` }) var vm = new Vue({ el:"#root", }) </script> </body> </html>
Vue中的作用域插槽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue中的作用域插槽(slot)</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <child> <template slot-scope="props"> <li>{{props.item}} -- hello</li> </template> </child> </div> <script> Vue.component('child', { data: function () { return { list: [1, 2, 3, 4] } }, template: `<div> <ul> <slot v-for="item of list" :item=item></slot> </ul> </div>` }) var vm = new Vue({ el: "#root", }) </script> </body> </html>
Vue的动态组件与 v-once 指令
点击按钮实现两个组件显隐切换
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>VUE的动态组件与v-once指令</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <child-one v-if="type === 'child-one'"></child-one> <child-two v-if="type === 'child-two'"></child-two> <button @click="handleBtnClick">change</button> </div> <script> Vue.component('child-one',{ template:"<div>child-one</div>" }) Vue.component('child-two',{ template:"<div>child-two</div>" }) var vm = new Vue({ el:'#root', data:{ type:'child-one' }, methods:{ handleBtnClick:function(){ this.type = this.type === 'child-one'?'child-two':'child-one' }, } }) </script> </body> </html>
动态组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>VUE的动态组件与v-once指令</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- component是vue自带的,表示动态组件 --> <component :is="type"></component> <!-- <child-one v-if="type === 'child-one'"></child-one> <child-two v-if="type === 'child-two'"></child-two> --> <button @click="handleBtnClick">change</button> </div> <script> Vue.component('child-one',{ template:"<div>child-one</div>" }) Vue.component('child-two',{ template:"<div>child-two</div>" }) var vm = new Vue({ el:'#root', data:{ type:'child-one' }, methods:{ handleBtnClick:function(){ this.type = this.type === 'child-one'?'child-two':'child-one' }, } }) </script> </body> </html>
V-once 节约性能,提高静态文件的展示效率
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>VUE的动态组件与v-once指令</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <!-- component是vue自带的,表示动态组件 --> <!-- <component :is="type"></component> --> <child-one v-if="type === 'child-one'"></child-one> <child-two v-if="type === 'child-two'"></child-two> <button @click="handleBtnClick">change</button> </div> <script> Vue.component('child-one',{ template:"<div v-once>child-one</div>" }) Vue.component('child-two',{ template:"<div v-once>child-two</div>" }) var vm = new Vue({ el:'#root', data:{ type:'child-one' }, methods:{ handleBtnClick:function(){ this.type = this.type === 'child-one'?'child-two':'child-one' }, } }) </script> </body> </html>