【技术实战】Vue在SaaS产品中的工程实践

阅读 1529
收藏 0
2017-07-18
原文链接: mp.weixin.qq.com

本文由薪人薪事高级前端工程师奕智编辑整理,全体前端同学倾情贡献 ~ 


薪人薪事的技术团队,是一支“团结、紧张、严肃、活泼”的队伍,尤其在前端领域以挑战导向著称:高速迭代、平稳交付、技能专精。

在以X-One为代号里程碑战役中,FE团队在保证新功能输出的情况下,历经几个月时间,完成了一次从JQuery到Vue的大型迁移,成为业界第一家使用Vue在支撑复杂SaaS业务的系统,同时产品体验得到广泛好评,团队能力得到明显提升。以下为整个实践历程,Enjoy:

第一步:产品逻辑拆解

薪人薪事的系统,主要是在以列表数据构成,包括筛选和搜索,如图:

一般每个模块的入口都是一个list列表页面,其中月份相关数据筛选,员工信息搜索,员工信息筛选,与数据表都有联动关系。

聪明的你可以发现,月份筛选(1)一次只能筛选一个,而员工信息筛选(3)也就是那个”漏斗“一次可以筛多个,而这些都会反映在下面的搜索筛选展示区域内(5),当然,搜索关键词也反映在(5)内,而每次点击搜索筛选后,都要发送ajax请求list数据渲染表格。这就造成了(1)(2)(3)与(4)与(5)的联动关系。


下面是员工信息筛选(3)的逻辑图,表现了“展示筛选结果”,“筛选弹出框”,“表格”之间的数据流转与事件逻辑:


这样我们就会发现,不同组件通过vuex进行连接,vuex就像一条水管,把数据流源源不断的输送给各个组件,然后各个组件只要根据各自的需求进行处理,就能在低耦合的情况下轻松实现很多复杂的联动效果。

第二步:技术选型比较

从业务逻辑的角度,项目涉及大量dom操作,原来jq的方法存取数据很没效率 

原来项目以jq为基础构建,由于涉及大量表单操作与表格交互,每次都要花大量时间将数据以属性的方式存入dom,当用到的时候又要花很大力气去读,经常能在页面上看到很多data-key,data-id,甚至$开头的读写属性的代码能占到业务量的20%;而每次工程流畅大概是这样:

这样做一个直观的后果就是,我们在操作数据的时候也在操作dom(暂时还不考虑多次操作dom带来的性能问题),这种对于简单的表单操作来说还好,但对于逻辑性强的交互来说实在是灾难,更不要说后期维护。

而换成vue后,数据流操作与dom操作是完全分开的,我们只需要关心数据,这在开发上极大的节省了我们的时间。

在我们使用vue的过程中,只需要关注数据操作,vue就会自动帮我们将数据的改变映射到dom层面上,代码从此没有大量的dom操作,变得简洁,好维护简直不能再爽。

从技术实现的角度,而原来jq的数据管理没有体系

jq并没有给我们提供一套数据管理的框架,当然我们可以自己实现一个mvc或者mvvm框架,或者用别人写好框架,比如升级到backbone,但这样不如一步到位,直接选用vue。当我们写jq的时候,数据的逻辑是于dom绑定在一起,既操作数据,又操作dom,而中间过程只能通过把数据存放在dom中的方式不让数据丢失。

而技术上来讲,数据于数据之间是没有联系的,每次都需要通过事件去改变数据以及由此影响到的其他数据,而由于没有数据管理框架,这种数据与数据之间的联系还得去专门找一块地方函数化(像vue的computed),更不用说还得处理数据变化带来的dom变化,实在很难维护。而vue我们有现成的数据管理体系,computed,methods,watch结合,再加上vuex,使我们的数据流转非常灵活,也就是数据与数据之间的联系非常容易整理。

同时dom的变化又是与数据的变化分开的,这样就不会遇到jq中需要在事件中同时处理数据,关联数据和dom的问题,如果要用jq去实现数据与html模版的绑定,就像上面说的又要花很大力气,即使用了handlerbar这样的模版引擎,也只是首次加载,之后每次还要重复做填入操作。


第三步:突破困难开动

在产品分析清楚,技术选型确定的情况下,就是轰轰烈烈开搞了。工程实践中,vue组件化成为核心,在使用jq的时候我们也可以去利用字符串拼接的方式构建组件,但这种方式不够成熟。而在vue中,我们有现成的组件构建方式,去把组件分成业务组件与基础组件,并且保证其可复用性与适配多种场景。

基础组件我们一部分用到了elementUI,但并不能完全适配我们的系统,尤其是在遇到一些需要异步加载数据以及根据条件禁止操作的情况,就需要我们自己去构建组件:

  • 我们把基础组件单独提出来放在与img,css一个层级的文件夹中,类似涉及表单的按钮,滑块,select/input在构建中要注意与数据来源的解耦,不能依赖vuex,保留尽量多的数据接口,又要考虑是否臃肿,尤其要考虑到一些异步以及禁止点击的情况。另外,可以利用每个组件默认带有的input事件与v-model属性的结合来完成一些类似打开关闭弹窗的操作。

  • 业务组件都是直接放在各个模块的目录下,对于我们业务来说,通常每个模块都会由一个list页面做为入口,之后通过list页面上的入口进入不同的路由,也就是子组件。大的功能组件之间的联系通过vuex来解决,比如员工模块通过list中的一条进入员工详情组件,这个员工id的流转就是通过vuex来完成(而不是写在路由上)。而对于可复用的业务组件来说,vuex就不建议使用。

同时,我们直接引入webpack一步到位,相对于原来jq+require的技术体系,有以下的好处:

  • 之前项目必须需要后端的接口,现在可以利用mook以及webpack起的服务脱离后端开展前端项目。

  • 资源的引入加载方式大大简化,最直观的来讲,不再需要一堆link标签,requirejs的config文件里一堆js依赖设置,直接在入口文件里把需要的import进来就好,甚至,由于系统是多页应用,每个模块的html又大致相同,所以所有模块的html文件都可以用一个ejs代替,然后在webpack中配置好就行,这样大大降低了维护成本,但由于import方式引入会被webpack打包,所以要注意资源的重复引用问题。当然,一些大的资源包并不希望与业务js代码打包到一起 因为会增加js包最终的大小,影响资源加载速度,高并发下占用带宽过大,所以较大的包会以标签引用的形式从cdn拉取,webpack中使用externals声明资源包引用关系,从而达到不影响其他模块import的情况下抽离较大资源包。

  • 资源分片,由于资源加载我们既想减少请求数,又不能忽略一个问题就是首屏数据ajax需要在js被解析之后才能发送出去,这时候就需要把一些大的插件打包出去,尽量减少单个包的体积,对于一些过大的包还要进行拆包操作,能做到首屏请求数比过去jq项目少,速度还快,这些事情都可以在wepack的帮助下完成。

  • 统一管理项目中的注释,console,以及异步组件的管理。


第四步:持续精细优化

资源加载是首要解决的问题

刚开始的时候很多引用的大的js都与业务代码一起打包,导致每次需要加载的js文件大小都在1M以上,严重拖慢的网络资源的加载速度。后来解决方法就是将这些大的js包单独提取,比如我们用的一个表格包叫handsontable,单独提取后,为保证引用依赖关系,这个handsontable会被加载到html的head中引入,而我们的业务代码放到body最后引用。这样既保证了依赖,又能将两者分开,而这些工作都是webpack帮我们完成。


    首先制定外部的引用,以及在ejs模版中加入;然后加入一些优化项的配置,完善异步组件:

    于是我们得到了非常显著的优化成果,首先是包的大小:

    优化之前:

    优化之后:

    这样的话,我们就可以减少同步资源的大小,最直接的好处就是ajax请求能够被尽早发出,页面加载的速度也被加快了。

    优化之前的请求:

    优化之后的请求


    然后是区分ajax事件与普通事件

    原来ajax会写在handle事件里没有分开,当我们使用jq的时候没有注意这点(应该被注意到),vue使事件方法与ajax方法这些不同功能的函数逻辑清晰,是我们自然而然的将事件方法强制加上handle,普通ajax方法前强制加上ajax,降低耦合性,并使业务逻辑清晰。甚至,我们可以把ajax方法放在vuex的actions.js或者单独一个js文件管理这些方法,用dispatch的方法调用这些ajax,就使.vue文件只写事件相关的方法以及一些工具函数,数据交互这块有专门的地方管理。

    另外,我们可以发现,前端这块涉及数据的主要操作只有两块,一个是通过ajax传递获取的,并能影响数据库的数据,另外就是我们页面上的效果开关变量。后者往往占用了我们大量的开发时间,vue能够很好地解决这个问题。而对于前者(ajax获取的数据),由于有了promise,我们建议将每个ajax都包上一层return promise,这样做的好处有两个,第一个并且最重要的是,我们能对所有ajax在业务方面进行统一管理,尤其是涉及错误处理,以及监控方面;第二个就是我们往往在实际业务中,handler事件与ajax方法是分开写的,return promise很必要,所以我们不如强制所有ajax都加上。

    实战总结

    vue的数据流控制是非常关键的点,尤其需要重点关注是可写还是只读:

    从效果上看,总体效果非常正向,vue迁移方案让我们的工程容易,简化了至少30%的工程时间(与jq对比),能够使我们专注业务,当然在整体在vue组件上的运用还有很大空间,尤其是针对重表单表格操作这种类型的组件上,还可以继续深挖。