浅谈Vue组件在实际项目中的应用

标签: 前端开发 | 发表时间:2017-07-21 01:12 | 作者:甄玉磊
出处:http://jdc.jd.com

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 结构为:

<!--2级部门-->
<div class="department">
    <people-part v-bind:info="departmentInfo" v-bind:num="0"></people-part>
    <!--3级部门-->
    <template v-for="(list,index) in departmentInfo.childList">
            <people-part v-bind:info="list" v-bind:num="1"></people-part>
            <!--4级部门-->  
            <template v-for="(list2,index2) in list.childList">
                    <people-part v-bind:info="list2"  v-bind:num="1"></people-part>
                    <!--5级部门-->
                    <template v-for="(list3,index3) in list2.childList">
                            <people-part v-bind:info="list3"  v-bind:num="1"></people-part>
                    </template>
            </template>
    </template>  
</div>

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

<script type="text/x-template" id="people-part-template">
    <div>
        <!--子组件内容-->
    </div> 
</script>

Vue 组件结构如下:

TimLine图片20170715153611

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

使用 slot 优化组件层级

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

693756-20170715155526087-1334117881

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

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

<script type="text/x-template" id="dialog-box-template">
    <div class="dialog-wrap" v-if="showDialog">
        <div class="dialog-is-distribute" :style="dialogStyle">
            <div class="title"><b :class="showIcon"></b>{{data.title}}<i class="close" v-on:click="closedialog()"></i></div>
            <dialog-add  v-if="status==0"></dialog-add>
            <dialog-delete v-else-if="status==1"></dialog-delete>
            <dialog-tab  v-else-if="status==2"></dialog-tab>
            <dialog-change  v-else-if="status==3"></dialog-change>
        </div>
    </div>
</script>

HTML 中定义组件代码为:

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

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

图9:Vue组件结构图

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

<script type="text/x-template" id="dialog-box-template">
    <transition name="fade">
        <div class="dialog-wrap" v-if="showDialog">
            <div class="dialog-is-distribute" :style="dialogStyle">
                <div class="title"><b :class="showIcon"></b>{{data.title}}<i class="close" v-on:click="closedialog()"></i></div>
                <component :style="contentStyle"  :is="type" :data="data.params" @close="closedialog" @action="action"></component>   
            </div>
        </div>
    </transition>
</script>

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

  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

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

<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>

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

<script type="text/x-template" id="dialog-pox-template">
    <div class="dialog-wrap">
        <div class="dialog-is-distribute" :style="{width:configure.width}">
            <div class="title"><b></b>
            <slot name="title">标题</slot>
            <i class="close" @click="close()"></i></div>
            <slot name="body"></slot>
            <slot name="btn">
                <div class="btn-part">
                    <button class="btn-add" @click="close()">确认</button>
                    <button class="btn-cancel" @click="close()">取消</button>
                </div>
            </slot>
        </div>
    </div>
</script>

引入 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() 函数即可,不必从子组件中层层触发父组件中函数;

总结:

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

相关 [vue 项目 应用] 推荐:

浅谈Vue组件在实际项目中的应用

- - JDC | 京东设计中心
Vue.js 是一套构建用户界面的渐进式框架,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. 虽然目前 Vue 已经很火了,但不可否认的是,仍有很多人刚刚开始学习使用 Vue 来构建前端项目,从生疏的初学者到熟练运用 Vue 的过程中,不可避免地会走一些弯路. 为了实现某个功能,也许尝试过很多方法,最终蓦然回首,才发现当初犯下的错误是那么幼稚.

微豆 - Vue 2.0 实现豆瓣 Web App 教程

- - SegmentFault 最新的文章
一个使用 Vue.js 与 Material Design 重构 豆瓣 的项目. 项目网站 http://vdo.ralfz.com/. # 克隆项目到本地 git clone https://github.com/RalfZhang/Vdo.git # 安装依赖 npm install # 在 localhost:8080 启动项目 npm run dev.

vue快速入门的三个小实例

- - SegmentFault 最新的文章
用vue做项目也有一段时间了,之前也是写过关于vue和webpack构建项目的相关文章,大家有兴趣可以去看下 webpack+vue项目实战(一,搭建运行环境和相关配置)(这个系列一共有5篇文章,这是第一篇,其它几篇文章链接就不贴了). 但是关于vue入门基础的文章,我还没有写过,那么今天就写vue入门的三个小实例,这三个小实例是我刚接触vue的时候的练手作品,难度从很简单到简单,都是入门级的.

xssProject在java web项目中应用

- - Java - 编程语言 - ITeye博客
1.项目引入xssProtect-0.1.jar、antlr-3.0.1.jar、antlr-runtime-3.0.1.jar包. * 覆盖getParameter方法,将参数名和参数值都做xss过滤. * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
.

在 Web 项目中应用 Apache Shiro

- - 企业架构 - ITeye博客
Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能. 认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁. Web 应用程序一般做法通过表单提交用户名及密码达到认证目的. “授权”即是否允许已认证用户访问受保护资源. 关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍 Shiro 在 Web Application 中如何实现验证码认证以及如何实现单点登录.

《孙子兵法》在敏捷项目管理中的应用

- - 博客 - 伯乐在线
来源: 黄文海@ibm_developerWorks. 简介: 《孙子兵法》中的论述虽然是关于战争的,但是其思想在项目管理领域对我们也是有借鉴意义的. 本文以笔者的实际项目管理经验为基础,分享了《孙子兵法》在敏捷项目管理中的应用. 希望能够对读者的实际项目管理工作有所启发. 成为“敏捷”,而不是做“敏捷”.

Mozilla的新项目 —— 把 OpenGL 应用导出成 WebGL

- - HTML5研究小组
1月28日,Mozilla的工程师Ehsan Akhgari通过 WebGL公共邮件列表发布了他们正在做的一个新项目,它可以自动将使用C/C++编写的OpenGL应用导出成为使用JavaScript的WebGL应用. 这个项目是建立在一个免费开源的C/C++到JavaScript编译器—— Emscripten的基础上的,Mozilla的计划是将其扩展从而支持OpenGL.

常见算法在实际项目中的应用

- - 博客 - 伯乐在线
近日Emanuele Viola在Stackexchange上提了这样的一个问题,他希望有人能够列举一些目前软件、硬件中正在使用的算法的实际案例来证明算法的重要性,对于大家可能给到的回答,他还提出了几点要求:. 使用这些算法的软件或者硬件应该是被广泛应用的;. 例子需要具体,并给出确切的系统、算法的引用地址;.

再看知名应用背后的第三方开源项目

- - 移动开发 - ITeye博客
知名应用程序的设计和技术一直都是开发者需要学习的,同样这些应用所使用的开源框架也是不可忽视的一部分. 此前《 iOS第三方开源库的吐槽和备忘》中作者ibireme列举了国内多款知名应用所使用的开源框架,并对其中一些框架进行了分析,同样国外开发者 @iOSCowboy也在博客中给我们列出了国外多款知名应用使用的开源框架.

Java自定义异常在项目中的应用

- - Java - 编程语言 - ITeye博客
在Java的一些项目中,在需要提供对外接口时,常常会有必要自定义响应一些code和message(例:0000:Success,500:Error),特别是在对接移动端项目中最为常见. 为更加方便提供这些接口的程序员的开发,可以应用Java的自定义异常处理来实现. 现有一移动端应用,需要对接我们项目,其中有一个用户登录接口,其接口的请求和响应参数如下:.