组件化开发 ⭐ 1. Vue 组件化思想⭐
组件化是Vuejs中的重要思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
任何的应用都会被抽象成一颗组件树
组件化思想的应用:
有了组件化的思想,我们在之后的开发中就要充分的利用它。
尽可能的将页面拆分成一个个小的,可复用的组件。
这样让我们的代码更加方便组织和管理,并且扩展性也更强。
2.注册组件的基本步骤⭐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn > </cpn > <cpn > </cpn > <cpn > </cpn > <cpn > </cpn > </div > </body > <script src ="../js/vue.js" > </script > <script > const cpn = Vue .extend ({ template : ` <div> <h2>注册组件步骤解析</h2> <p> 1. Vue.extend(): 调用 Vue.extend() 创建的是一个组件构造器 通常在组件构造器时,传入template代表我们自定义组件的模板 该模板就是在使用组件的地方,要显示的HTML代码</p> <p>2.Vue.component(): 调用Vue.component()是将干柴的组件构造器注册为一个组件,并且给它起一个组件的标签名称,所以需要传递俩 个参数:1、注册组件的标签名,2、组件构造器</p> <p>3. 组件必须挂载在某个Vue实例下,否则它不会生效。</p> </div>` }) const app = new Vue ({ el : '#app' , data : { message : 'hello vue' , }, components : { cpn } }) </script > </html >
2.1 注册组件步骤解析
2.2 组件注册方式:全局组件 、局部组件 组件注册有俩种方式:
建议把通用的组件定义在全局,把不通用的组件定义在局部
全局组件 注册:
1 2 3 4 5 6 7 8 Vue.component("my-component", { template: "<div > A custom component!</div > " }); // 创建根实例 new Vue({ el: "#example" });
在模板中使用组件:
1 2 3 <div id ="example" > <my-component > </my-component > </div >
渲染结果:
1 2 3 <div id ="example" > <div > A custom component!</div > </div >
总结:
可以在任何组件中被使用的组件(就好比全局变量)
如果应用中把所有组件都定义成全局组件,名字就不能有冲突
使用场景:多个页面都需要使用的组件建议定义成全局组件
局部组件 你不必把每个组件都注册到全局,你可以通过某个Vue实例/组件的实例选项 components
注册仅在其作用域中可用的组件:
注册:
1 2 3 4 5 6 7 8 9 new Vue ({ components : { "my-component" : { template : "<div>A custom component!</div>" } } });
使用:
1 2 3 4 5 <div id ="example" > <div > A custom component!</div > <my-component > </my-component > </div >
总结:
只能在它的父组件(定义所属的组件)中被使用,不会污染全局(就好比函数内部定义的变量)
使用组件的时候,会先在自己的 components
中找,如果找不到,直奔全局找
局部组件 只能 在父组件中被使用, 爷爷、后代。。都不行
使用场景:不需要在其他组件中被使用的组件建议定义成局部
3.组件中的模板⭐ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn > </cpn > </div > <template id ="cpn" > <div > <h2 > {{title}}</h2 > <p > 哈哈哈哈</p > </div > </template > </body > <script src ="../js/vue.js" > </script > <script > Vue .component ('cpn' , { template : '#cpn' , data ( ) { return { title : '组件模板的分离写法' } } }) const app = new Vue ({ el : '#app' , data : { message : 'hello vue' , } }) </script > </html >
小结:
字符串模板 template
优点:合适快速学习测试,方便快捷
缺点:编辑器无法提供高亮显示,智能提示等
.vue 单文件组件
优点:更好的语法高亮,智能提示等
缺点:需要配合打包工具使用
script 标签模板(了解,不常用)
4.组件中的data为什么必须是一个函数?⭐
首先,如果不是一个函数,Vue直接就会报错。
其次,原因是在于Vue让每个组件对象都会返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn > </cpn > <cpn > </cpn > <cpn > </cpn > </div > </body > <template id ="cpn" > <div > <h2 > 当前计数:{{current}}</h2 > <button @click ="add" > +</button > <button @click ="sub" > -</button > <p > 如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data, 类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。 而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。 </p > </div > </template > <script src ="../js/vue.js" > </script > <script > Vue .component ('cpn' , { template : '#cpn' , data ( ) { return { current : 0 } }, methods : { add ( ) { this .current ++ }, sub ( ) { this .current -- } } }) const app = new Vue ({ el : '#app' , data : { message : 'hello vue' , } }) </script > </html >
5.组件通信⭐ 组件就像零散的积木,我们需要把这些积木按照一定的规则拼装起来,而且要让它们互相之间能进行通讯,这样才能构成一个有机的完整系统。
在真实的应用中,组件最终会构成树形结构,就像人类社会中的家族树一样:
在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。
相应地,组件之间有以下几种典型的通讯方案:
直接的父子关系 (访问双方对象里面的一些方法)
父组件通过 this.$refs
访问子组件
子组件通过 this.$parent
访问父组件
直接父子关系 (获取双方里面的数据)
父组件通过Props 给子组件下发数据
子组件通过事件方式给父组件发送消息
没有直接关系
简单场景:借助于事件机制进行通讯
复杂场景:使用状态管理容器
利用 cookie 和 localstorage 进行通讯
利用 session 进行通讯
无论你使用什么前端框架,组件之间的通讯都离不开以上几种方案,这些方案与具体框架无关
5.1 父传子 组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的。
5.1.1 props 的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn :cmessage ="message" :ccities ="cities" > </cpn > </div > <template id ="cpn" > <div > <h2 > {{ccities}}</h2 > <h1 > {{cmessage}}</h1 > <P v-for ="item in ccities" > {{item}}</P > </div > </template > </body > <script src ="../js/vue.js" > </script > <script > const cpn = { template : '#cpn' , props : { cmessage : { type : String , default : '默认值aa' , }, ccities : { type : Array , default () { return ['初始1' , '初始2' , '初始3' ] }, } } } const app = new Vue ({ el : '#app' , data : { message : 'hello vue' , cities : ['北京' , '上海' , '深圳' ] }, components : { cpn } }) </script > </html >
5.1.2 props数据验证
我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。 要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组:
当需要对props进行类型等验证时,就需要对象写法了
验证都支持哪些数据类型呢?
String
Number
Boolean
Array
Object
Date
Function
Symbol
当我们有自定义构造函数时,验证也支持自定义的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Vue .component ("example" , { props : { propA : Number , propB : [String , Number ], propC : { type : String , required : true }, propD : { type : Number , default : 100 }, propE : { type : Object , default : function ( ) { return { message : "hello" }; } }, propF : { validator : function (value ) { return value > 10 ; } } } });
5.2 子传父 5.2.1 使用 自定义事件 来完成 $emit
props 用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
这个时候,我们需要用 自定义事件
来完成
什么时候需要自定义事件呢?
当子组件需要向父组件传递数据时,就要用自定义事件了
我们之前学习的 v-on 不仅仅可以用于监听DOM事件,也可以用于组件的自定义事件
自定义事件的流程:
在子组件中,通过 $emit() 来触发事件。
在父组件中,通过 v-on 来监听子组件事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn @itemclick ="cpnClick" > </cpn > </div > <template id ="cpn" > <div > <button v-for ="item in categories" @click ='btnClick(item)' > {{item.name}}</button > </div > </template > </body > <script src ="../js/vue.js" > </script > <script > const cpn = { template : '#cpn' , data ( ) { return { categories : [{ id : 1 , name : '热门推荐' }, { id : 2 , name : '数码相机' }, { id : 3 , name : '电脑办公' }, { id : 4 , name : '家用电器' }, ] } }, methods : { btnClick (item ) { this .$emit('itemclick' , item) } } } const app = new Vue ({ el : '#app' , data : { message : 'hello vue' , }, components : { cpn }, methods : { cpnClick (item ) { console .log ('cpnClick' , item); } } }) </script > </html >
子组件: 向 父组件用 $emit 发射一个自定义事件
1 2 3 4 5 6 methods : { btnClick (item ) { this .$emit('itemclick' , item) } }
父组件模板 : 用v-on来监听子组件
1 2 3 4 <div id ="app" > <cpn @itemclick ="cpnClick" > </cpn > </div >
父组件 : 在父组件模板中用 v-on来监听子组件事件 然后 用自定义的事件来获取子组件的数据
1 2 3 4 5 methods : { cpnClick (item ) { console .log ('cpnClick' , item); } }
5.3 父子组件通信-结合双向绑定案例
以下案例 实现 子传父 父传子 以及 上边的值 是下边的 100倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <cpn v-bind:number1 ="num1" v-bind:number2 ="num2" @num1change ='num1change' @num2change ='num2change' > </cpn > </div > <template id ="cpn" > <div > <h1 > props:{{number1}}</h1 > <h1 > data:{{dnumber1}}</h1 > <input type ="text" :vlaue ="dnumber1" @input ="num1Input" > <h1 > props:{{number2}}</h1 > <h1 > data:{{dnumber2}}</h1 > <input type ="text" :vlaue ="dnumber2" @input ="num2Input" > </div > </template > </body > <script src ="../js/vue.js" > </script > <script > const app = new Vue ({ el : '#app' , data : { num1 : 0 , num2 : 0 }, methods : { num1change (value ) { this .num1 = Number (value) }, num2change (value ) { this .num2 = Number (value) } }, components : { cpn : { template : '#cpn' , props : { number1 : Number , number2 : Number }, data ( ) { return { dnumber1 : this .number1 , dnumber2 : this .number2 } }, methods : { num1Input (event ) { this .dnumber1 = event.target .value this .$emit('num1change' , this .dnumber1 ) this .dnumber2 = this .dnumber1 * 100 this .$emit('num2change' , this .dnumber2 ) }, num2Input (event ) { this .dnumber2 = event.target .value this .$emit('num2change' , this .dnumber2 ) this .dnumber1 = this .dnumber2 / 100 this .$emit('num1change' , this .dnumber1 ) } } } } }) </script > </html >
5.4 父子组件的访问
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children或$refs
子组件访问父组件:使用$parent
5.4.1 $children
this.$children是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的message状态。
$children的缺陷:
通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值 ,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用**$refs**
5.4.2 $refs
$refs的使用:
$refs和ref指令通常是一起使用的。
首先,我们通过ref给某一个子组件绑定一个特定的ID。
其次,通过this.$refs.ID就可以访问到该组件了。