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

标签: 前端开发 | 发表时间:2017-07-21 09: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 的过程中,不可避免地会走一些弯路. 为了实现某个功能,也许尝试过很多方法,最终蓦然回首,才发现当初犯下的错误是那么幼稚.

总结4个方面优化Vue项目

- - IT瘾-jianshu
1、使用v-if代替v-show. 两者的区别是:v-if不渲染DOM,v-show会预渲染DOM. 除以下情况使用v-show,其他情况尽量使用v-if. 2、v-for必须加上key,并避免同时使用v-if. 一般我们在两种常见的情况下会倾向于这样做:. 为了过滤一个列表中的项目. 比如 v-for="user in users" v-if="user.isActive".

vue项目你一定会用到的性能优化!

- - 掘金 前端
提起 性能优化 很多人眼前浮现的面试经验是不是历历在目呢. 反正,性能优化在我看来他永远是前端领域的 热度之王. 而本渣最近维护的项目恰巧在这个方向下了很大功夫,一些经验之谈奉上,希望对大家有些许帮助. 既然说性能优化,那他总得有一个公认的标准,这就是我们很多次听到的 Lighthouse. 在很多单位,都有着自己的性能监控平台,我们只需要引入相应的sdk,那么在平台上就能分析出你页面的存在的性能问题,大家是不是学的很神奇.

Electron+Vue开发跨平台桌面应用

- - SegmentFault 最新的文章
虽然B/S是目前开发的主流,但是C/S仍然有很大的市场需求. 受限于浏览器的沙盒限制,网页应用无法满足某些场景下的使用需求,而桌面应用可以读写本地文件、调用更多系统资源,再加上Web开发的低成本、高效率的优势,这种跨平台方式越来越受到开发者的喜爱. Electron是一个基于Chromium和 Node.js,使用 HTML、CSS和JavaScript来构建跨平台应用的跨平台开发框架,兼容 Mac、Windows 和 Linux.

超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!

- - 掘金后端
项目代码: github.com/MarkerHub/v…. 项目视频: www.bilibili.com/video/BV1PQ…. 文章总体分为2大部分,Java后端接口和vue前端页面,比较长,因为不想分开发布,真正想你4小时学会,哈哈. 从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等.

ssr vuejs/vue-hackernews-2.0: HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering

- -
This is a demo primarily aimed at explaining how to build a server-side rendered Vue app, as a companion to our SSR documentation. #install dependenciesnpm install#or yarn#serve in dev mode, with hot reload at localhost:8080npm run dev#build for productionnpm run build#serve in production modenpm start.

Vue 移动端框架

- - IT瘾-jianshu
vonic 一个基于 vue.js 和 ionic 样式的 UI 框架,用于快速构建移动端单页应用,很简约. 中文文档| github地址| 在线预览. vux 基于WeUI和Vue(2.x)开发的移动端UI组件库. 基于webpack+vue-loader+vux可以快速开发移动端页面,配合vux-loader方便你在WeUI的基础上定制需要的样式.

vue路由权限校验

- - 掘金前端
做后台系统的时候,难免会有用户权限的判断. admin可以查看全部菜单,user只能查看部分菜单. 一开始接触这个需求的时候,完全是纯前端做的. 在配置路由的时候,加一个roles的属性,通过判断用户的roles是否与路由的roles属性相匹配来作为显示隐藏的依据. // 过滤路由 menuList-菜单 roles-用户角色 const checkMenuList = (menuList, roles) => { for (let i = 0; i < menuList.length; i++) {.

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 中如何实现验证码认证以及如何实现单点登录.