Vue组件化开发
1、Vue组件化开发思想。
引述:组件化规范Web Components。
1)、我们希望尽可能多的重用代码。
2)、自定义组件的方式不太容易(html、css、js)。
3)、多次使用组件可能导致冲突。
4)、Web Components通过创建封装好功能的定制元素解决上述问题。
5)Vue部分实现了上述Web Components规范。
2、Vue组件注册。Vue组件注册注意事项。
1)、data必须是一个函数。分析函数与普通对象的对比,Vue的data是一个对象,区别于组件的data是一个函数。组件的data是函数,可以形成一个闭包的环境,这可以保证每一个组件都可以拥有一份独立的数据。
2)、组件模板内容必须是单个根元素,分析演示实际的效果,比如多个div包了多个button标签。类比Vue实例的el容器中。
3)、组件模板内容可以是模板字符串。模板字符串需要浏览器提供支持(ES6语法规则)。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <div v-text="msg"></div> 10 11 <!-- Vue组件注册成功之后,就可以使用了 --> 12 <!-- 这个组件是一个子组件,因为Vue也是一个组件,子组件写到Vue父组件当中,形成父子关系。 --> 13 <button-counter></button-counter> 14 15 <!-- 组件的重用,可以拷贝多份,每个组件相互独立,不互相影响的。 --> 16 <button-counter></button-counter> 17 <button-counter></button-counter> 18 </div> 19 20 <script src="vue.js" type="text/javascript"></script> 21 <script type="text/javascript"> 22 // Vue的组件化开发,Vue的注册。参数一是组件的名称,参数二是组件的内容,是一个对象。 23 // Vue.component('', { 24 // data: '', //data表示组件数据。 25 // template: '', // template表示组件模板内容。可以做数据的绑定、分支、事件的操作、循环的结构都可以在这里面使用。 26 // }); 27 // Vue的组件化开发,Vue的注册,下面的语法就将组件注册成功了。 28 // Vue.component('button-counter', { 29 // data: function() { 30 // // 提供一个具体的对象,对象当中存放的具体的数据 31 // return { 32 // count: 0, 33 // } 34 // }, 35 // // template: '<button @click="count++">点击了{{count}}次</button>', 36 // // 组件模板内容必须是单个根元素,分析演示实际的效果 37 // template: '<div><button @click="counts">点击了{{count}}次</button><button @click="counts">点击了{{count}}次</button></div>', 38 // methods: { 39 // counts: function() { 40 // this.count++; 41 // } 42 // } 43 // }) 44 //--------------------------------------------------------------------------------------- 45 Vue.component('button-counter', { 46 data: function() { 47 // 提供一个具体的对象,对象当中存放的具体的数据 48 return { 49 count: 0, 50 } 51 }, 52 // template: '<button @click="count++">点击了{{count}}次</button>', 53 // 组件模板内容可以是模板字符串。模板字符串需要浏览器提供支持(ES6语法规则) 54 template: ` 55 <div> 56 <button @click="counts">点击了{{count}}次</button> 57 <button @click="counts">点击了{{count}}次</button> 58 </div> 59 `, 60 methods: { 61 counts: function() { 62 this.count++; 63 } 64 } 65 }) 66 67 // 创建Vue对象 68 var vm = new Vue({ 69 el: '#app', 70 data: { // 对象,区别于组件的data是一个函数。 71 msg: 'hello world!', 72 }, 73 methods: {} 74 }); 75 </script> 76 </body> 77 </html>
3、Vue组件注册,组件名称方法。
1)、短横线方式。Vue.component('button-counter', {/** ... */});
2)、驼峰方式,驼峰方式要用到根组件里面,需要换成短横线方式。如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件。Vue.component('buttonCounter', {/** ... */});
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <Hello-world></Hello-world> 10 <div v-text="msg"></div> 11 12 <!-- Vue组件注册成功之后,就可以使用了 --> 13 <!-- 这个组件是一个子组件,因为Vue也是一个组件,子组件写到Vue父组件当中,形成父子关系。 --> 14 <button-counter></button-counter> 15 16 <!-- 组件的重用,可以拷贝多份,每个组件相互独立,不互相影响的。 --> 17 <button-counter></button-counter> 18 <button-counter></button-counter> 19 </div> 20 21 <script src="vue.js" type="text/javascript"></script> 22 <script type="text/javascript"> 23 // Vue的组件化开发,Vue的注册。参数一是组件的名称,参数二是组件的内容,是一个对象。 24 // Vue.component('', { 25 // data: '', //data表示组件数据。 26 // template: '', // template表示组件模板内容。可以做数据的绑定、分支、事件的操作、循环的结构都可以在这里面使用。 27 // }); 28 // Vue的组件化开发,Vue的注册,下面的语法就将组件注册成功了。 29 // Vue.component('button-counter', { 30 // data: function() { 31 // // 提供一个具体的对象,对象当中存放的具体的数据 32 // return { 33 // count: 0, 34 // } 35 // }, 36 // // template: '<button @click="count++">点击了{{count}}次</button>', 37 // // 组件模板内容必须是单个根元素,分析演示实际的效果 38 // template: '<div><button @click="counts">点击了{{count}}次</button><button @click="counts">点击了{{count}}次</button></div>', 39 // methods: { 40 // counts: function() { 41 // this.count++; 42 // } 43 // } 44 // }) 45 46 Vue.component('HelloWorld', { 47 data: function() { 48 return { 49 msg: '您好,Vue!', 50 } 51 }, 52 template: '<div>{{msg}}</div>', 53 }); 54 55 //--------------------------------------------------------------------------------------- 56 Vue.component('buttonCounter', { 57 data: function() { 58 // 提供一个具体的对象,对象当中存放的具体的数据 59 return { 60 count: 0, 61 } 62 }, 63 // template: '<button @click="count++">点击了{{count}}次</button>', 64 // 组件模板内容可以是模板字符串。模板字符串需要浏览器提供支持(ES6语法规则) 65 template: ` 66 <div> 67 <button @click="counts">点击了{{count}}次</button> 68 <button @click="counts">点击了{{count}}次</button> 69 <HelloWorld></HelloWorld> 70 </div> 71 `, 72 methods: { 73 counts: function() { 74 this.count++; 75 } 76 } 77 }) 78 79 // 创建Vue对象 80 var vm = new Vue({ 81 el: '#app', 82 data: { // 对象,区别于组件的data是一个函数。 83 msg: 'hello world!', 84 }, 85 methods: {} 86 }); 87 </script> 88 </body> 89 </html>
4、Vue组件注册,局部组件注册。
1)、局部组件,只能在注册他的父组件当中使用,在别的组件中是不可以使用的。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 使用局部组件 --> 10 <component-a></component-a> 11 <component-b></component-b> 12 <component-c></component-c> 13 </div> 14 15 <script src="vue.js" type="text/javascript"></script> 16 <script type="text/javascript"> 17 /* 通过这种方式注册的组件只能在父组件中使用,在别的组件中是使用不了的。 */ 18 var componentA = { 19 data: function() { 20 return { 21 msg: 'hello world componentA.', 22 } 23 }, 24 template: '<div>{{msg}}</div>', 25 }; 26 var componentB = { 27 data: function() { 28 return { 29 msg: 'hello world componentB.', 30 } 31 }, 32 template: '<div>{{msg}}</div>', 33 }; 34 var componentC = { 35 data: function() { 36 return { 37 msg: 'hello world componentC.', 38 } 39 }, 40 template: '<div>{{msg}}</div>', 41 }; 42 43 // 创建Vue对象 44 var vm = new Vue({ 45 el: '#app', 46 data: { // 对象,区别于组件的data是一个函数。 47 48 }, 49 methods: { 50 51 }, 52 // Vue的局部组件注册。 53 components: { 54 // 将局部组件通过components注册进来 55 'componentA': componentA, 56 'componentB': componentB, 57 'componentC': componentC 58 } 59 }); 60 </script> 61 </body> 62 </html>
5、Vue组件注册,Vue调式工具vue-devtools用法。
1)、克隆地址,https://github.com/vuejs/vue-devtools.git
2)、安装依赖包npm install。
3)、构建npm run build。生成一个Chrome扩展的包。
4)、打开Chrome扩展页面。加载构建的包。
5)、选中开发者模式。
6)、加载已解压的扩展,选择shells/chrome。
6、Vue组件,组件之间的关系,包含父子关系(祖孙关系)和兄弟关系。组件间数据交互。
1)、父组件向子组件传值。
a、组件内部通过props接收传递过来的值,它的值是一个数组,数组中可以包含很多的属性,这些属性都是从父组件传输过来的。
b、父组件通过属性将值传递给子组件。通过静态传递和动态绑定传递属性。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 父组件打印 --> 10 <div>{{pmsg}}</div> 11 <!-- 子组件的使用,父组件以属性的方式将值传递给子组件 --> 12 <menu-item title="我是来自父组件的标题" concent="我是父组件的内容!"></menu-item> 13 <!-- 父组件给子组件动态传值 --> 14 <menu-item v-bind:title="title" concent="我是父组件的内容!"></menu-item> 15 <menu-item :title="title" concent="我是父组件的内容!"></menu-item> 16 </div> 17 18 <script src="vue.js" type="text/javascript"></script> 19 <script type="text/javascript"> 20 // 创建一个组件,父组件向子组件传值。 21 Vue.component('menu-item', { 22 props: ['title', 23 'concent' 24 ], // 子组件接收父组件传递的值 25 data: function() { 26 return { 27 msg: '子组件本身的数据' 28 } 29 }, 30 template: '<div>{{msg + " : " + title + " " + concent}}</div>', 31 }); 32 33 // 创建Vue对象 34 var vm = new Vue({ 35 el: '#app', 36 data: { // 对象,区别于组件的data是一个函数。 37 pmsg: '父组件中的内容', 38 title: '我是来自父组件的标题!', 39 }, 40 methods: { 41 42 }, 43 44 }); 45 </script> 46 </body> 47 </html>
2)、props属性名规则。
a、在props中使用驼峰形式,在html模板中需要使用短横线的形式。因为dom元素的属性不区分大小的,如果传递驼峰形式就出现问题了。
b、字符串形式的模板中没有这个限制。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 父组件打印 --> 10 <div>{{pmsg}}</div> 11 <!-- 子组件的使用,父组件以属性的方式将值传递给子组件 --> 12 <menu-item menu-title="我是来自父组件的标题" concent="我是父组件的内容!"></menu-item> 13 14 <!-- 在html模板中需要使用短横线的形式 --> 15 <!-- 父组件给子组件动态传值,title值是动态传值,是父组件里面定义的变量 --> 16 <menu-item v-bind:menu-title="title" concent="我是父组件的内容!"></menu-item> 17 <menu-item :menu-title="title" concent="我是父组件的内容!"></menu-item> 18 </div> 19 20 <script src="vue.js" type="text/javascript"></script> 21 <script type="text/javascript"> 22 // 创建一个组件,父组件向子组件传值。 23 Vue.component('third-component', { 24 // 子组件接收父组件传递的值 25 props: ['thirdTitle'], 26 // 子组件接收父组件传递的值menuTitle、concent就可以使用了。 27 // 这里面指定的就是字符串形式的模板。这里面可以使用驼峰形式的数据是没有问题的。 28 template: '<div>{{thirdTitle}}</div>', 29 }); 30 Vue.component('menu-item', { 31 // 子组件接收父组件传递的值 32 props: ['menuTitle', 33 'concent' 34 ], 35 data: function() { 36 return { 37 msg: '子组件本身的数据' 38 } 39 }, 40 // 子组件接收父组件传递的值menuTitle、concent就可以使用了。 41 // 这里面指定的就是字符串形式的模板。这里面可以使用驼峰形式的数据是没有问题的。 42 template: '<div>{{msg + " : " + menuTitle + " " + concent}}<third-component thirdTitle="hello"></third-component></div>', 43 }); 44 45 // 创建Vue对象 46 var vm = new Vue({ 47 el: '#app', 48 data: { // 对象,区别于组件的data是一个函数。 49 pmsg: '父组件中的内容', 50 title: '我是来自父组件的标题!', 51 }, 52 methods: { 53 54 }, 55 56 }); 57 </script> 58 </body> 59 </html>
3)、props属性值规则。
a、字符串String。
b、数值Number。
c、布尔值Boolean。
d、数组Array。
e、对象Object。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 第二步、在父组件进行打印输出 --> 10 <div>{{pmsg}}</div> 11 12 <!-- 第二步、在子组件进行引用父组件的数据pstr --> 13 <menu-item v-bind:pstr2='pstr' v-bind:pnum2='pnum' v-bind:pboo2='pboo' v-bind:parr2='parr' v-bind:pobj2='pobj'> 14 </menu-item> 15 16 <br /> 17 <menu-item :pstr2='pstr' :pnum2='pnum'></menu-item> 18 <br /> 19 20 <!-- pnum2前面不加冒号就是字符串类型的,加上冒号就是数值number类型 --> 21 <menu-item v-bind:pstr2='pstr' pnum2='pnum'></menu-item> 22 </div> 23 24 <script src="vue.js" type="text/javascript"></script> 25 <script type="text/javascript"> 26 // 父组件向子组件传值props属性名类型 27 Vue.component('menu-item', { 28 /* 第三步、在子组件进行引用父组件传来的数据 */ 29 props: ['pstr2', 'pnum2', 'pboo2', 'parr2', 30 'pobj2' 31 ], 32 template: ` 33 <div> 34 <div>{{pstr2}}</div> 35 <div>{{typeof pnum2}}</div> 36 <div>{{pboo2}}</div> 37 <div>{{parr2}}</div> 38 <ul> 39 <li v-bind:key='index' v-for='(item,index) in parr2'>{{item}}</> 40 </ul> 41 <div> 42 <span>{{pobj2.name}}</span> 43 <span>{{pobj2.age}}</span> 44 </div> 45 </div> 46 ` 47 }); 48 49 // 创建Vue对象 50 var vm = new Vue({ 51 el: '#app', 52 data: { // 对象,区别于组件的data是一个函数。 53 pmsg: '父组件中内容', // 第一步、在父组件中进行打印显示 54 pstr: 'hello', // 第一步、在子组件进行打印显示,字符串类型 55 pnum: 12, //数组类型 56 pboo: true, //布尔类型 57 parr: ['apple', 'orange', 'banana'], //数组类型 58 pobj: { 59 name: '张飒飒', 60 age: 25, 61 }, 62 }, 63 methods: { 64 65 }, 66 }); 67 </script> 68 </body> 69 </html>
7、Vue组件,子组件向父组件传值。
1)、props传值数据原则,单向数据流,意思就是只允许父组件向子组件传递数据,而不允许子组件直接操作props中的数据。如果子组件直接操作props中的数据,数据的控制逻辑就比较复杂,不容易进行控制,单向数据流处理逻辑比较清晰,所以推荐使用单向数据流。
2)、Vue子组件向父组件传值的方式,是通过子组件通过自定义事件向父组件传递信息。$emit方法名称携带一个参数,这个参数名称就是自定义事件,这个事件就可以传递给父组件,父组件需要监听这个事件,父组件通过v-on:事件名称,直接绑定处理事件的名称,后面跟着事件处理逻辑。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 第二步、在父组件进行打印输出 --> 10 <!-- v-bind:style绑定样式字体大小,可以通过子组件向父组件传递信息,通过自定义事件 --> 11 <div v-bind:style='{fontSize:fontSize2 + "px"}'>{{pmsg}}</div> 12 13 <!-- 第二步、在子组件进行引用父组件的数据pstr --> 14 <menu-item v-bind:parr2='parr' v-on:enlarge-text='handle'></menu-item> 15 <br /> 16 <menu-item v-bind:parr2='parr' @enlarge-text='handle'></menu-item> 17 </div> 18 19 <script src="vue.js" type="text/javascript"></script> 20 <script type="text/javascript"> 21 // 父组件向子组件传值props属性名类型 22 Vue.component('menu-item', { 23 /* 第三步、在子组件进行引用父组件传来的数据 */ 24 props: ['parr2'], 25 template: ` 26 <div> 27 <ul> 28 <li v-bind:key='index' v-for='(item,index) in parr2'>{{item}}</li> 29 </ul> 30 <button @click='parr2.push("lemon")'>点击</button> 31 32 <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button> 33 </div> 34 ` 35 /* <button @click='$emit("")'>扩大父组件中字体大小</button>中的$emit("")固定方法名称,参数是自定义事件的名称 */ 36 /* 然后在父组件中进行自定义事件的监听, */ 37 }); 38 39 // 创建Vue对象 40 var vm = new Vue({ 41 el: '#app', 42 data: { // 对象,区别于组件的data是一个函数。 43 pmsg: '父组件中内容', // 第一步、在父组件中进行打印显示 44 parr: ['apple', 'orange', 'banana'], //数组类型 45 fontSize2: 10, 46 }, 47 methods: { 48 // Vue子组件向父组件传值的方式 49 handle: function() { 50 // 扩大字体大小 51 this.fontSize2 += 1; 52 } 53 }, 54 }); 55 </script> 56 </body> 57 </html>
3)、子组件通过自定义事件向父组件传值。子组件通过自定义事件向父组件传递信息,$emit方法名称可以携带两个参数,第二个参数可以是传递给父组件的参数。在父组件中通过$event接收到子组件传输的数据,$event是固定写法。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <!-- 第二步、在父组件进行打印输出 --> 10 <!-- v-bind:style绑定样式字体大小,可以通过子组件向父组件传递信息,通过自定义事件 --> 11 <div v-bind:style='{fontSize:fontSize2 + "px"}'>{{pmsg}}</div> 12 13 <!-- 第二步、在子组件进行引用父组件的数据pstr --> 14 <menu-item v-bind:parr2='parr' v-on:enlarge-text='handle($event)'></menu-item> 15 <br /> 16 <!-- $event是固定写法,接收子组件传递的参数值 --> 17 <menu-item v-bind:parr2='parr' @enlarge-text='handle($event)'></menu-item> 18 </div> 19 20 <script src="vue.js" type="text/javascript"></script> 21 <script type="text/javascript"> 22 // 父组件向子组件传值props属性名类型 23 Vue.component('menu-item', { 24 /* 第三步、在子组件进行引用父组件传来的数据 */ 25 props: ['parr2'], 26 template: ` 27 <div> 28 <ul> 29 <li v-bind:key='index' v-for='(item,index) in parr2'>{{item}}</li> 30 </ul> 31 <button @click='parr2.push("lemon")'>点击</button> 32 33 <button @click='$emit("enlarge-text",100)'>扩大父组件中字体大小</button> 34 </div> 35 ` 36 /* <button @click='$emit("")'>扩大父组件中字体大小</button>中的$emit("")固定方法名称,参数是自定义事件的名称 */ 37 /* 然后在父组件中进行自定义事件的监听, */ 38 /* $emit固定方法,第二个参数可以是子组件向父组件传递的参数值 */ 39 }); 40 41 // 创建Vue对象 42 var vm = new Vue({ 43 el: '#app', 44 data: { // 对象,区别于组件的data是一个函数。 45 pmsg: '父组件中内容', // 第一步、在父组件中进行打印显示 46 parr: ['apple', 'orange', 'banana'], //数组类型 47 fontSize2: 10, 48 }, 49 methods: { 50 // Vue子组件向父组件传值的方式 51 handle: function(val) { 52 // 扩大字体大小 53 this.fontSize2 += val; 54 } 55 }, 56 }); 57 </script> 58 </body> 59 </html>
8、Vue组件,组件间数据交互,非父子组件间传值。
1)、单独的事件中心管理组件间的通信。比如兄弟组件之间的数据交互,此时使用props和自定义事件就不好使了,此时就要使用事件中心的管理模式。比如组件A和组件B需要通过事件中心,组件A触发事件,组件B监听事件中心的事件,反之亦然。
2)、监听事件与销毁事件。事件中心通过var eventHub = new Vue()即可。eventHub.$on('add-todo',addTodo)用于监听事件,参数1是自定义事件的名称,参数2是事件函数。eventHub.$off('add-todo')用于销毁时间事件,参数1是事件的名称。
3)、触发事件。eventHub.$emit('add-todo',id);参数一,触发指定的事件名称,参数二是携带的参数。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <div> 10 <span>事件中心的事件销毁</span> 11 <button @click="handle">销毁</button> 12 </div> 13 14 <br /> 15 <menu-tom></menu-tom> 16 17 <br /> 18 <!-- 兄弟组件,相互传递数据 --> 19 <menu-jack></menu-jack> 20 </div> 21 22 <script src="vue.js" type="text/javascript"></script> 23 <script type="text/javascript"> 24 // 提供Vue的事件中心,在事件中心可以进行事件的监听 25 var hub = new Vue(); 26 27 // 兄弟之间组件数据传递 28 Vue.component('menu-tom', { 29 data: function() { 30 return { 31 num: 0, 32 } 33 }, 34 template: ` 35 <div> 36 <div>Tom:{{num}}</div> 37 <div> 38 <button @click='handle'>点击</button> 39 </div> 40 </div> 41 `, 42 /* 点击按钮触发事件 */ 43 methods: { 44 handle: function() { 45 // 触发兄弟组件的事件 46 hub.$emit('jack-event', 5); 47 } 48 }, 49 // Vue的钩子函数,Vue声明周期里面的,mounted钩子函数一旦被触发,模板就就绪了,即可以对模板进行操作了。 50 mounted: function() { 51 // 监听事件,箭头函数,接收对方传递过来的数据 52 hub.$on('tom-event', (val) => { 53 // 拿到这对方的数据之后,进行累加操作,val是兄弟组件传递过来的 54 this.num += val; 55 }); 56 } 57 }); 58 59 // 兄弟组件 60 Vue.component('menu-jack', { 61 data: function() { 62 return { 63 num: 0, 64 } 65 }, 66 template: ` 67 <div> 68 <div>Jack:{{num}}</div> 69 <div> 70 <button @click='handle'>点击</button> 71 </div> 72 </div> 73 `, 74 /* 点击按钮触发事件 */ 75 methods: { 76 handle: function() { 77 // 触发兄弟组件的事件 78 hub.$emit('tom-event', 10); 79 } 80 }, 81 // Vue的钩子函数,Vue声明周期里面的,mounted钩子函数一旦被触发,模板就就绪了,即可以对模板进行操作了。 82 mounted: function() { 83 // 监听事件,箭头函数,接收对方传递过来的数据 84 hub.$on('jack-event', (val) => { 85 // 拿到这对方的数据之后,进行累加操作,val是兄弟组件传递过来的 86 this.num += val; 87 }); 88 } 89 }); 90 91 // 创建Vue对象 92 var vm = new Vue({ 93 el: '#app', 94 data: { // 对象,区别于组件的data是一个函数。 95 96 }, 97 methods: { 98 handle: function() { 99 // 事件中心的事件销毁 100 hub.$off('tom-event'); 101 hub.$off('jack-event'); 102 } 103 }, 104 }); 105 </script> 106 </body> 107 </html>
9、Vue组件,组件插槽。组件插槽的作用。
1)、父组件向子组件传递内容,这个内容是模板的内容。上面分析的是数据的交互。
2)、子组件使用<slot></slot>预留插槽,可以将父组件的中标签之间的内容展示出来。
3)、插槽的位置位于子组件的模板中,使用<slot></slot>表示,语法固定。
4)、使用的时候,使用这个组件的时候通过标签中的内容传递给<slot></slot>表示。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <menu-alert>此处有Bug!!!</menu-alert> 10 <menu-alert>此处有Warn!!!</menu-alert> 11 </div> 12 13 <script src="vue.js" type="text/javascript"></script> 14 <script type="text/javascript"> 15 // 提供Vue的事件中心,在事件中心可以进行事件的监听 16 var hub = new Vue(); 17 18 // 兄弟之间组件数据传递 19 Vue.component('menu-alert', { 20 /* 插槽的内容是在组件标签的中间传递过来的,如果不传递内容,这里有默认显示 */ 21 template: ` 22 <div> 23 <strong>Error:</strong> 24 <slot>默认内容!</slot> 25 </div> 26 `, 27 }); 28 29 // 创建Vue对象 30 var vm = new Vue({ 31 el: '#app', 32 data: { // 对象,区别于组件的data是一个函数。 33 34 }, 35 methods: { 36 37 38 }, 39 }); 40 </script> 41 </body> 42 </html>
10、Vue组件,组件插槽。具名插槽用法。
1)、在子组件模板定义的时候,使用<slot name="插槽名称"></slot>来定义,也可以省略name的属性定义,就是默认插槽。
2)、在父组件中使用的时候,在标签上面使用slot="插槽名称"就可以使用该插槽了。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 </head> 7 <body> 8 <div id="app"> 9 <menu-alert> 10 <p slot='header'>标题信息</p> 11 </menu-alert> 12 <menu-alert> 13 <p>详细内容1</p> 14 <p>详细内容2</p> 15 </menu-alert> 16 <menu-alert> 17 <p slot='footer'>底部信息</p> 18 </menu-alert> 19 20 <br /> 21 22 <menu-alert> 23 <!-- template临时包裹内容,并不会渲染到界面上,可以同时把多条内容填充到文本中 --> 24 <template slot='header'></template> 25 <p>标题信息1</p> 26 <p>标题信息2</p> 27 </menu-alert> 28 <menu-alert> 29 <p>详细内容1</p> 30 <p>详细内容2</p> 31 </menu-alert> 32 <menu-alert> 33 <template slot='footer'> 34 <p>底部信息1</p> 35 <p>底部信息2</p> 36 </template> 37 38 </menu-alert> 39 </div> 40 41 <script src="vue.js" type="text/javascript"></script> 42 <script type="text/javascript"> 43 // 提供Vue的事件中心,在事件中心可以进行事件的监听 44 var hub = new Vue(); 45 46 // 兄弟之间组件数据传递 47 Vue.component('menu-alert', { 48 /* 插槽的内容是在组件标签的中间传递过来的,如果不传递内容,这里有默认显示 */ 49 template: ` 50 <div> 51 <header> 52 <slot name='header'></slot> 53 </header> 54 <main> 55 <slot></slot> 56 </main> 57 <footer> 58 <slot name='footer'></slot> 59 </footer> 60 </div> 61 `, 62 }); 63 64 // 创建Vue对象 65 var vm = new Vue({ 66 el: '#app', 67 data: { // 对象,区别于组件的data是一个函数。 68 69 }, 70 methods: { 71 72 73 }, 74 }); 75 </script> 76 </body> 77 </html>
11、Vue组件,组件插槽。作用域插槽。
1)、应用场景,父组件对子组件的内容进行加工处理。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title></title> 6 <style type="text/css"> 7 .orange { 8 color: orange; 9 } 10 </style> 11 </head> 12 <body> 13 <div id="app"> 14 <!-- 应用场景,父组件对子组件的内容进行加工处理。 --> 15 <fruit-list v-bind:list2='list'> 16 <!-- <template slot-scope='slotProps'> --> 17 <!-- 父组件提供template标签,来进行插槽的填充,slot-scopek属性可以得到子组件中绑定的那个属性,即子组件绑定的v-bind:info='item'的属性 --> 18 <template slot-scope='slotProps'> 19 <!-- 就得到了子组件的数据了,就可以开始对子组件的数据进行加功处理了。 --> 20 <strong v-bind:class="orange2" v-if='slotProps.info.id==2'> 21 {{slotProps.info.name}} 22 </strong> 23 24 </template> 25 26 </fruit-list> 27 <br /> 28 </div> 29 30 <script src="vue.js" type="text/javascript"></script> 31 <script type="text/javascript"> 32 // 提供Vue的事件中心,在事件中心可以进行事件的监听 33 var hub = new Vue(); 34 35 // 兄弟之间组件数据传递 36 Vue.component('fruit-list', { 37 props: ['list2'], 38 /* 插槽的内容是在组件标签的中间传递过来的,如果不传递内容,这里有默认显示 */ 39 template: ` 40 <div> 41 <li v-bind:key='item.id' v-for='(item,id) in list2'> 42 <slot v-bind:info='item'>{{item.name}}</slot> 43 </li> 44 </div> 45 `, 46 }); 47 48 // 创建Vue对象 49 var vm = new Vue({ 50 el: '#app', 51 data: { // 对象,区别于组件的data是一个函数。 52 orange2: 'orange', 53 list: [{ 54 id: 1, 55 name: 'apple', 56 }, { 57 id: 2, 58 name: 'orange', 59 }, { 60 id: 3, 61 name: 'banana', 62 }, ] 63 }, 64 methods: { 65 66 67 }, 68 }); 69 </script> 70 </body> 71 </html>
12、基于组件的案例,按照组件化方式实现业务需求。
1)、根据业务功能进行组件化划分。
a、标题组件,展示文本。
b、列表组件,列表展示,商品数量变更,商品删除。
c、结算组件,计算商品金额。
2)、功能实现步骤。
a、实现整体布局和样式效果。
b、划分独立的功能组件。
c、组合所有的子组件形成整体结构。
d、逐个实现各个组件的功能,包含标题组件,列表组件,结算组件。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <style type="text/css"> 7 .container {} 8 9 .container .cart { 10 width: 300px; 11 /*background-color: lightgreen;*/ 12 margin: auto; 13 } 14 15 .container .title { 16 background-color: lightblue; 17 height: 40px; 18 line-height: 40px; 19 text-align: center; 20 /*color: #fff;*/ 21 } 22 23 .container .total { 24 background-color: #FFCE46; 25 height: 50px; 26 line-height: 50px; 27 text-align: right; 28 } 29 30 .container .total button { 31 margin: 0 10px; 32 background-color: #DC4C40; 33 height: 35px; 34 width: 80px; 35 border: 0; 36 } 37 38 .container .total span { 39 color: red; 40 font-weight: bold; 41 } 42 43 .container .item { 44 height: 55px; 45 line-height: 55px; 46 position: relative; 47 border-top: 1px solid #ADD8E6; 48 } 49 50 .container .item img { 51 width: 45px; 52 height: 45px; 53 margin: 5px; 54 } 55 56 .container .item .name { 57 position: absolute; 58 width: 90px; 59 top: 0; 60 left: 55px; 61 font-size: 16px; 62 } 63 64 .container .item .change { 65 width: 100px; 66 position: absolute; 67 top: 0; 68 right: 50px; 69 } 70 71 .container .item .change a { 72 font-size: 20px; 73 width: 30px; 74 text-decoration: none; 75 background-color: lightgray; 76 vertical-align: middle; 77 } 78 79 .container .item .change .num { 80 width: 40px; 81 height: 25px; 82 } 83 84 .container .item .del { 85 position: absolute; 86 top: 0; 87 right: 0px; 88 width: 40px; 89 text-align: center; 90 font-size: 40px; 91 cursor: pointer; 92 color: red; 93 } 94 95 .container .item .del:hover { 96 background-color: orange; 97 } 98 </style> 99 </head> 100 <body> 101 <div id="app"> 102 <div class="container"> 103 <my-cart></my-cart> 104 </div> 105 </div> 106 <script type="text/javascript" src="js/vue.js"></script> 107 <script type="text/javascript"> 108 var CartTitle = { 109 props: ['username'], 110 template: ` 111 <div class="title">{{username }} 的商品</div> 112 ` 113 }; 114 var CartList = { 115 props: ['lists'], 116 template: ` 117 <div> 118 <div v-bind:key='id' v-for='(item,id) in lists' class="item"> 119 <img v-bind:src="item.img" /> 120 <div class="name">{{item.name}}</div> 121 <div class="change"> 122 <a href="" @click.prevent='sub(item.id)'>-</a> 123 <input type="text" class="num" v-bind:value='item.num' @blur='changeNum(item.id,$event)' /> 124 <a href="" @click.prevent='add(item.id)'>+</a> 125 </div> 126 <div class="del" @click='del(item.id)'>×</div> 127 </div> 128 </div> 129 `, 130 methods: { 131 del: function(id) { 132 // 不建议直接在子组件中操作props: ['lists'],的数据。将id传给父组件,让父组件进行删除。 133 console.log(id); 134 // 将id传递给父组件,通过自定义事件的方式向父组件传递信息 135 this.$emit('cart-del', id); 136 }, 137 /* 通过事件对象$event可以将参数传递过来 */ 138 changeNum: function(id, event) { 139 // 在父组件进行处理商品数量的变更,告诉父组件改的数量也要传递过去。 140 console.log(id, event.target.value); 141 // 触发自定义事件 142 this.$emit('change-num', { 143 id: id, 144 num: event.target.value, 145 type: 'change', 146 }); 147 }, 148 sub: function(id) { 149 this.$emit('change-num', { 150 id: id, 151 type: 'sub', 152 }); 153 }, 154 add: function(id) { 155 this.$emit('change-num', { 156 id: id, 157 type: 'add', 158 }) 159 }, 160 } 161 }; 162 var CartTotal = { 163 props: ['lists'], 164 template: ` 165 <div class="total"> 166 <span>总价:{{totalPrice}}</span> 167 <button>结算</button> 168 </div> 169 `, 170 // 计算属性 171 computed: { 172 totalPrice: function() { 173 // 计算商品的总价,遍历数组,让单价乘以数量再累加。 174 var total = 0; 175 this.lists.forEach(item => { 176 // 计算出总价 177 total += item.price * item.num; 178 }); 179 // 将计算的总价返回。 180 return total; 181 } 182 } 183 }; 184 185 // 全局组件,这个全局组件里面包含三个局部子组件。 186 Vue.component('my-cart', { 187 data: function() { 188 return { 189 uname: '张飒飒', 190 list: [{ 191 id: 1, 192 name: 'TCL彩电', 193 price: 1000, 194 num: 1, 195 img: 'img/a.jpg' 196 }, { 197 id: 2, 198 name: '机顶盒', 199 price: 1000, 200 num: 1, 201 img: 'img/b.jpg' 202 }, { 203 id: 3, 204 name: '海尔冰箱', 205 price: 1000, 206 num: 1, 207 img: 'img/c.jpg' 208 }, { 209 id: 4, 210 name: '小米手机', 211 price: 1000, 212 num: 3, 213 img: 'img/d.jpg' 214 }, { 215 id: 5, 216 name: 'PPTV电视', 217 price: 1000, 218 num: 2, 219 img: 'img/e.jpg' 220 }] 221 } 222 }, 223 /* 通过父组件监听子组件的删除事件,将id传输给delCart方法的$event参数 */ 224 template: ` 225 <div class="cart"> 226 <cart-title v-bind:username='uname'></cart-title> 227 <cart-list v-bind:lists='list' v-on:cart-del='delCart($event)' @change-num='changeNum($event)'></cart-list> 228 <cart-total v-bind:lists='list'></cart-total> 229 </div> 230 `, 231 components: { 232 'cart-title': CartTitle, 233 'cart-list': CartList, 234 'cart-total': CartTotal, 235 }, 236 methods: { 237 delCart: function(id) { 238 console.log(id); 239 // 通过id删除list集合中的数据,第一步,找到id所对应数据的索引,第二步,根据索引删除对应的数据。 240 // 第一步,找到id所对应数据的索引。 241 var index = this.list.findIndex(item => { 242 return item.id == id; 243 }); 244 245 // 第二步,根据索引删除对应的数据 246 this.list.splice(index, 1); 247 }, 248 changeNum: function(val) { 249 console.log(val); 250 // 分为三种情况,输入域变更,加好变更,减号变更。 251 if (val.type == 'change') { 252 // 将子组件传递过来的数据num更新list中对应的数据 253 this.list.some(item => { 254 // 如果集合里面的和子组件传递过来的匹配相等就进行更新即可。 255 if (item.id == val.id) { 256 // 此时就完成了更新 257 item.num = val.num; 258 // 终止遍历 259 return true; // 返回true表示终止遍历 260 } 261 }); 262 } else if (val.type == 'add') { 263 this.list.some(item => { 264 // 如果集合里面的和子组件传递过来的匹配相等就进行更新即可。 265 if (item.id == val.id) { 266 // 此时就完成了更新 267 item.num += 1; 268 // 终止遍历 269 return true; // 返回true表示终止遍历 270 } 271 }); 272 } else if (val.type == 'sub') { 273 this.list.some(item => { 274 // 如果集合里面的和子组件传递过来的匹配相等就进行更新即可。 275 if (item.id == val.id) { 276 // 此时就完成了更新 277 item.num -= 1; 278 // 终止遍历 279 return true; // 返回true表示终止遍历 280 } 281 }); 282 } 283 } 284 } 285 }); 286 287 var vm = new Vue({ 288 el: '#app', 289 data: { 290 291 } 292 }); 293 </script> 294 </body> 295 </html>
效果,如下所示: