Vue.js 是一套构建用户界面的渐进式框架,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。——摘自 Vue 官网。

虽然目前 Vue 已经很火了,但不可否认的是,仍有很多人刚刚开始学习使用 Vue 来构建前端项目,从生疏的初学者到熟练运用 Vue 的过程中,不可避免地会走一些弯路。为了实现某个功能,也许尝试过很多方法,最终蓦然回首,才发现当初犯下的错误是那么幼稚。然而,这些错误也正是通往成功道路上的奠基石,因此,总结这个过程很有必要。本文基于参与过的项目——绩效管理与人才库项目,总结了在实际项目中使用 Vue 组件逐渐完善的过程,旨在抛砖引玉,共同学习。

合理的使用 Vue 子组件

初次使用 Vue 组件是在绩效管理项目中,由于当时对于父、子组件只是停留在基础概念上,在实际项目中没有合理构建父、子组件结构。首先看一下需要完成的页面效果:

4

图中最外层是二级部门,从外到里,一直嵌套到五级部门(为了更好的展示层级效果,这里把每级部门中的人员列表省略了)。当时分析每一级部门都是类似的,于是把每一层级部门作为一个子组件来构建,正如下图所示:左图为搭建的 Vue 组件结构图,右图为代码结构:

90

相信看到这里,各位看官已经发现了问题所在:二级部门子组件嵌套了三级部门的子组件,同时三级部门子组件嵌套了四级部门子组件,以此类推。这样导致子组件嵌套层数过多,父子组件之间、兄弟组件之间通信繁琐,并且也失去了组件的意义——即组件是可以扩展 HTML 元素,用来封装可重用的代码。

接下来,我们分析这样搭建组件带来的困难:也就是父子组件之间、兄弟组件之间通信繁琐的问题。因为很快我就遇到了这样一个需求,如下图所示:

693756-20170718174046177-110591891-690x457

每一级部门都会有人员信息,此时如果点击第五级部门中的“绩效评价”按钮,需要出现“评价与评级”的弹窗(右图),然而弹窗组件是放在最外层 Vue 实例中的,与二级部门子组件形成并列关系,其 Vue 组件结构如下图所示:

92

这样一来,五级部门子组件中点击按钮要想触发弹窗组件,需要层层监听被触发的函数,直至传到根实例 Vue 中,再分配到弹窗组件中(注,这里采用的是父子组件通信方式 $emit ,因为尽可能的不要让子组件修改父组件中的数据,所以没有采用其他通信方式,例如直接修改父组件数据或者父组件的数据公共化等方法)。

根据 Vue 知识,父组件向子组件中使用 props 传递属性值,子组件向父组件中传递数据使用 $emit(eventName) 触发事件,父组件使用 $on(eventName) 监听事件。这样说可能有些糊涂,请看图解:

TimLine图片20170715144322

从图中可以看出,要想从五级部门和父组件中进行通信,需要层层上传数据,每一层通信函数都要在 HTML 和 JavaScript 中触发、监听, 何其繁琐!尤其务必注意的是,命名的规范问题,由于 HTML  属性会忽略大小写,父子间通信定义的函数名称可以使用中划线命名,但不能使用驼峰法定义,否则无法正常对通信函数触发和监听!笔者初期就因为在 JavaScript 中习惯使用驼峰法命名函数,结果在 HTML 中使用了驼峰法定义监听函数,导致无法监听到该事件,花费了很多时间来排除错误。 然而塞翁失马焉知非福,在解决父子组件通信的过程中,逐渐加深了对父子之间函数通信的理解。

优化方法

上面介绍了这么多在初次构建 Vue 组件时走过的“弯路”,那么如何去优化呢?我们再来看最开始要完成的页面:

2

上图为每个层级部门展开后的页面,从图中可以看出每个层级部门的页面很类似,要知道组件最主要的是用来封装可“重用”的代码!很明显标红色区域或者标蓝色区域都可以重复使用,为了最大限度的使用组件,避免重复,这里我们使用红色区域为子组件,HTML 结构为:

这样,我们只需要写一个子组件:

Vue 组件结构如下:

TimLine图片20170715153611

可以看出,部门内部的组件和弹窗均放在了根实例 Vue 下面,每级部门层级搭建好后,只需调用“部门内部子组件”。这样一来,部门内部子组件和弹窗组件之间的通信都化简了很多,部门内部子组件也可以方便的使用根实例 Vue 中的数据!

使用 slot 优化组件层级

在实际项目中,应尽量避免使用多层组件嵌套,但有的时候组件确实需要嵌套多层才能实现所需的功能,那么还能继续优化吗?我们再来看一个使用子组件的例子,在人才库项目中,多个页面出现了弹窗,有的同一个页面有多个弹窗,弹窗的样式如下图所示:

693756-20170715155526087-1334117881

分析这些弹窗的特点,发现有共同的区域,比如说有公共的头部,公共的边框,还有类似的按钮。但同时又存在差异的地方,弹出主体不同,大小不同,有的弹窗下部按钮数量不同。为了减少重复的开发,可以考虑将弹窗中通用的样式封装成一个子组件,弹窗剩下主体部分再具体开发。

按照上述思路,首先,编辑弹窗外层子组件:

HTML 中定义组件代码为:

上述代码定义了子组件为 <dialog-box> ,定义了 title 部分和外边框,内部不同部分嵌套不同的子组件来渲染,如 <dialog-add> 子组件、<dialog-delete> 等子组件渲染不同的主体部分,其结构见下图:

图9:Vue组件结构图

最后使用内置组件 <component> ,渲染一个“元组件”为动态组件,依据 is 的值,来决定哪个组件被渲染,从而进一步优化上述代码,其判断逻辑放在 JavaScript 中:

好了,现在实现了使用公共的弹窗部分,可以根据需求,通过开发不同的子组件来构建弹窗的主体等差异化部分。此时,我们来分析一下上述做法的缺点:

  1. 父子组件嵌套过多,增加了父子组件间通信的复杂度,弹窗公共部分 <dialog-box> 作为子组件,还嵌套着主体部分子组件才能实现弹窗主体部分差异化;
  2. 很多主体子组件部分的逻辑函数,例如点击“确定”、“取消”等按钮需要关闭弹窗,相同的功能,却要每个弹窗主体部分子组件中都要写一遍,并且还需要触发弹窗公共部分 <dialog-box> 的函数,然后才能触发到父组件中关闭弹窗的命令;693756-20170718154537146-892772573
  3. 从父组件往弹窗主体部分传递数据复杂,例如在父组件中,点击按钮后,触发 Ajax 请求,要想把处理后得到的数据,传递到主体部分的子组件中,首先需要父组件先将数据传递到弹窗外层组件中,然后才能使用 props(如下图定义的 props 参数 data )传递到内部主体子组件中。

55

综上所述,弹窗外层与主体间由于嵌套子组件,导致代码重复、父子组件通信复杂度增加。那么,说了这么多,有优化的方法吗?

优化方法

这时,Vue 中的 slot 分发机制登场了!什么是 slot 分发机制呢?官方定义:“为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为内容分发,Vue.js 实现了一个内容分发 API,使用特殊的 <slot> 元素作为原始内容的插槽 ”,说简单一些,其实 slot 相当于子组件中的占位符,如果父组件中有内容,则覆盖该占位符,否则显示该占位符内容。

693756-20170718161058708-1692428378

于是,根据上面的思想,我们可以重新定义弹窗组件,这次弹窗主体作为一个子组件,内部使用 slot 分别占位标题部分、主体 body 部分、底部按钮 btn 部分,如果父组件中没有定义该 slot ,则显示默认的子组件,否则渲染父组件定义的 slot 部分,其示意图如下:

2

自定义弹窗主体元素代码为:

在页面中定义子组件代码:

引入 CSS 样式后,效果如下图所示:

693756-20170718163814599-1632372400

弹窗组件中定义了三部分,如果要修改弹窗主体,则在<dialog-pox> 的 slot=body 中更换弹窗主体内容,如果不需要按钮部分,则在自定义元素中增加 <div slot=”btn”></div> ,替换子组件中 slot=btns 部分。此外,根据参数 configure 可以控制弹窗的宽度,颜色背景等属性。(还可以直接在<dialog-pox class=”newclass”>增加新的 className ,来生成自定义样式的弹窗)。

002

使用 slot 开发的优点有:

  1. 减少父子组件嵌套层数,只定义了弹窗主体子组件,其余部分在页面的 HTML 中定义;
  2. 弹窗主体可以直接使用父组件中 Ajax 返回的数据,例如 {{dataInfo.name}} 中的 dataInfo 就是父组件中的数据;
  3. 避免了重复定义函数,同样是关闭弹窗操作,只需执行 closeDialog() 函数即可,不必从子组件中层层触发父组件中函数;

总结:

综上所述,在实际项目应用中,为了实现一个效果,有可能走过很多弯路,最后回头去发现之前犯下的错误很是简单,甚至结论可以一语带过, 但是在这个过程中,也学习到了很多知识,甚至之所以有了这个过程,才会对知识的理解更加透彻,新的知识学起来有可能很快,真正用到项目中,却总是出现不可预期的错误,深刻体会到“纸上得来终觉浅,绝知此事要躬行” 。总之要不断的完善,总结,也许过不了多久,再次回顾发现目前的代码还能有优化的地方,这也正是我们成长必须要走的路程,愿与君共勉!

喜欢(29) 评论(0) 分享

Leave a Reply

© 2014 JDC. All Rights Reserved.