最近的学习工作中都用到了饿了么的 element-ui , 以及移动端的 Mint-ui , 借此机会,想深入学习 Vue 的组件开发流程及原理,故写此篇博客,记录所学及所感. 本例程以一个 table 组件为例,至于为什么想做这个 table 组件,是看到 element-ui 中有这么一个强大的 table 组件,感到很好奇。本篇讲解组件间通信 (父子 / 非父子), 以及运用 slot 的使用方法 (下篇).
基本构造 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 <template> <t-table :items ="items" border center > <t-table-item label ="操作" > <template scope ="prop" > <el-button type ="primary" @click ="showData(prop.index, prop.rowData)" > 删除{{prop.text}}</el-button > </template > </t-table-item > <t-table-item label ="数字" width ="120px" prop ="text" > </t-table-item > <t-table-item label ="年龄" width ="150px" prop ="age" > </t-table-item > <t-table-item label ="名字" prop ="name" > </t-table-item > </t-table > </template> <script > export default { data ( ){ return { items : [ { text : '123' , name : 'tzx' , age : 13 }, { text : '456' , name : 'hanya' , age :12 } ] } }, methods : { click ( ){ alert ('click' ) }, showData (index, data ){ console .log (index, data); } }, components : { }, mounted ( ){ console .log ('UlTest is mounted' ); } } </script >
所有代码可在这里 找到.
组件渲染顺序 在 Vue 中,组件的渲染顺序是由里向外的。在本例中,会先渲染 TTableItem, 其次是 TTable (如果里面有其他组件,会先渲染其他组件), 最后是 UlTest 组件. 下图是 Vue 组件的生命周期图示 由图可知,在 created 事件完成时,数据绑定和事件初始化完成,当允许编译模板时 (有 el 选项或者有 template 提供), 将模板编译成 render 函数,因此,当此时 render 函数中有其他组件时,会进入此组件的生命周期。所以,created 钩子是从外组件到内组件的,即外组件的 created 调用后,才可能进入内部其他组件的生命周期钩子函数如 created.
1 2 3 4 5 6 7 8 9 10 UlTest is createdTTable is createdTTableItem is createdTThead is created.TTbody is created4 TTableItem is mountedTThead is mountedTTbody is mounted111111TTable is mountedUlTest is mounted
对于 mounted 函数,在同一组件层面的组件,如 TTable 中的 TThead 和 TTbody, 先 created 的组件会先 mounted, 好理解吧?? 理解清楚这些周期函数有助于理解组件间通信的问题.
父子组件通信 先说说简单的,父子组件通信.
父对子通信 在最上面的 UlTest.vue 组件中,template 元素下直接包含一个 t-table 组件,可以理解为 t-table 是 UlTest 的子组件 (不晓得这么理解对不对). 对 t-table 组件,我们传递了 3 个参数,一个是绑定的 items 参数,两个是布尔值参数 (border center). 在 TTable.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 <script> import TTbody from './TTbody' import TThead from './TThead' export default { props : { items : { type : Array , default : ()=> [] }, border : { type : Boolean , }, center : { type : Boolean } }, tableId : 1 , components : { TTbody , TThead , }, data ( ){ return { dataTable : this .items , tableHeader : [], scopedSlot : null , } }, computed : { tableRow ( ){ return this .items ; }, _bus ( ){ return this .$refs .bus ; } }, created ( ){ this .$on('init' ,this .handle ) this .$on('initScope' ,this .handleScope ) console .log ('TTable is created' ); }, methods :{ handle (d ){ this .tableHeader .push (d); }, handleScope (d ){ if (d) { this .scopedSlot = d; } } }, mounted ( ){ console .log ('TTable is mounted' ); } } </script>
即在子组件中,通过定义 props 对象,即可完成子组件接受父组件的数据. props 可以简单地定义为字符串数组 (不能验证数据类型), 也可以定义为对象,每个键为接收的参数名,值对参数对象,可设置 type, required, default (数组 / 对象等引用类型的默认值需要使用函数返回数据),validator 等. 以上为父对子的通信,下面看子对父的通信方式.
子对父的通信 这里用一个简单的例子说明:
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 <div id="app" > <p > {{num}} </p > <t-t1 @change ="changeNum" > </t-t1 > <t-t2 > </t-t2 > </div> var bus = new Vue ();var TT1 = { data ( ) { return { num : 1 , }; }, methods : { handle (d ){ this .num = d; }, changeFa ( ){ this .$emit('change' ,666 ); } }, created ( ){ bus.$on('change' , this .handle ) }, template : `<div><p>{{this.num}}</p><button @click="changeFa">发送1</button></div>` , }; var TT2 = { data ( ) { return { num : 1 , }; }, methods : { send ( ){ bus.$emit('change' , 4 ); } }, template : `<div><p>{{this.num}}</p><button @click='send'>发送2</button></div>` , } var Main = { data ( ) { return { num :1 , }; }, methods : { changeNum (d ){ this .num = d; } }, components : { TT1 , TT2 , } } var Ctor = Vue .extend (Main )new Ctor ().$mount('#app' )
本例中,包含了非父子通信的简单实现 (分析见下节). 主要说子对父通信。在 tt1 组件中,注册 change 事件,绑定处理函数到父组件的 changeNum 函数上,当子组件通过 vm.$emit (‘change’, props) 触发时,会被父组件捕获并交由 changeNum 函数处理。即完成了子对父的通信.
非父子通信 如上一节中代码所示,可以 let bus = new Vue (); 用 bus 作为简单的中间处理系统。通过在不同组件中用 bus.$on/.$emit 注册 / 发送事件,实现任意组件间的通信.
需要注意的,在组件上注册事件时,尽量用一个单词或者是中划线格式的,如不能写 @changeNumber=”changeNum”, 可以改为 @change=”changeNum” 或者是 change-number=”changeNum”, 并且,$emit 中事件名分别对应’change’或者’change-number’
本篇完结,大家鼓掌!!!明天更新关于 slot/scopedSlot 的相关实践.