专栏名称: 京东设计中心JDC
专业,创造力,激情,设计。京东用户体验设计部门,致力于创造更美好的电子商务购物体验。
目录
相关文章推荐
51好读  ›  专栏  ›  京东设计中心JDC

JDC丨京东设计中心 - 浅谈Vue组件在实际项目中的应用

京东设计中心JDC  · 公众号  ·  · 2017-08-02 10:21

正文

请到「今天看啥」查看全文


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

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

合理的使用 Vue 子组件

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

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

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

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

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

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

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

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

优化方法

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

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

< div class = "department" >

< people - part v - bind : info = "departmentInfo" v - bind : num = "0" > people - part >

< template v - for = "(list,index) in departmentInfo.childList" >

< people - part v - bind : info = "list" v - bind : num = "1" > people - part >

< template v - for = "(list2,index2) in list.childList" >

< people - part v - bind : info = "list2" v - bind : num = "1" > people - part >

< template v - for = "(list3,index3) in list2.childList" >

< people - part v - bind : info = "list3" v - bind : num = "1" > people - part >

template >

template >

template >

div >

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


1

2

3

4

5

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

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


1

2

3

4

5

6

7

8

9

10

11

="status==2">dialog-tab>

            <dialog-change  v-else-if="status==3">dialog-change>

        div>

    </div>

cript>

HTML 中定义组件代码为:


1

<dialog-box :show="isDialogShow" :type="dialogType" :data="dialogData"  @close="closeDialog">dialog-box>

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

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


1

2

3

4

5

6

7

8

9

10

="dialog-wrap" v-if="showDialog">

            <div class="dialog-is-distribute" :style="dialogStyle">

                <div class="title"><b :class="showIcon"></b>{{data.title}}>div>

                <component :style="contentStyle"  :is="type" :data="data.params" @close="closedialog" @action="action">component>  

            div>

        div>

    </transition>

cript>

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

  1. 父子组件嵌套过多,增加了父子组件间通信的复杂度,弹窗公共部分 作为子组件,还嵌套着主体部分子组件才能实现弹窗主体部分差异化;

  2. 很多主体子组件部分的逻辑函数,例如点击“确定”、“取消”等按钮需要关闭弹窗,相同的功能,却要每个弹窗主体部分子组件中都要写一遍,并且还需要触发弹窗公共部分 的函数,然后才能触发到父组件中关闭弹窗的命令;

  3. 从父组件往弹窗主体部分传递数据复杂,例如在父组件中,点击按钮后,触发 Ajax 请求,要想把处理后得到的数据,传递到主体部分的子组件中,首先需要父组件先将数据传递到弹窗外层组件中,然后才能使用 props(如下图定义的 props 参数 data )传递到内部主体子组件中。

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

优化方法

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

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

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


1

2

3

4

5

6

7

8

9

10

11

12

<dialog-pox v-show="isDialogShow==1" v-on:closedia="closeDialog()" :configure="nature">

        <h2 slot="title">删除项h2>

        <div class="eval-content dialog-delete" slot="body">

            <p class="delete-content">确认删除{{dataInfo.name}}吗?p>

        div>

dialog-pox>

 

<dialog-pox v-show="isDialogShow==2" v-on:closedia="closeDialog()" :configure="nature">

        <h2 slot="title">添加项h2>

        <h1 slot="body">我是{{operation.doner}}-{{operation.deleter}}h1>

        <div slot="btn">div>

dialog-pox>

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


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

            <div class="title"><b></b>

            标题lot>

            <i class="close" @click="close()">i></div>

            lot>

            <slot name="btn">

                <div class="btn-part">

                    <button class="btn-add" @click="close()">确认button>

                    <button class="btn-cancel" @click="close()">取消button>

                </div>

            lot>

        div>

    </div>

cript>

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

弹窗组件中定义了三部分,如果要修改弹窗主体,则在 的 slot=body 中更换弹窗主体内容,如果不需要按钮部分,则在自定义元素中增加

,替换子组件中 slot=btns 部分。此外,根据参数 configure 可以控制弹窗的宽度,颜色背景等属性。(还可以直接在增加新的 className ,来生成自定义样式的弹窗)。

使用 slot 开发的优点有:

  1. 减少父子组件嵌套层数,只定义了弹窗主体子组件,其余部分在页面的 HTML 中定义;

  2. 弹窗主体可以直接使用父组件中 Ajax 返回的数据,例如 {{dataInfo.name}} 中的 dataInfo 就是父组件中的数据;

  3. 避免了重复定义函数,同样是关闭弹窗操作,只需执行 closeDialog() 函数即可,不必从子组件中层层触发父组件中函数;

总结:

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








请到「今天看啥」查看全文