一、背景

  目前公司的电子合同采用表单设计器+合同业务配合实现,做了半年多后终于上线,但是下边员工普遍反映卡顿,甚至卡死,爆栈。尤其是新增和修改合同页面,因为这部分数据量大,逻辑复杂,很容易崩溃,所以决定进行性能优化。

二、业务场景介绍

  先来了解一下我们是怎么实现:

  1. 因为我们公司合同变换频繁,条款之间还有逻辑,所以做了个基础服务(说白了就是组件库),为合同提供模板

  2. 表单设计器作为基础服务,打包成了组件库,嵌入到合同项目,包括合同生成组件(拖拽生成合同模板)和合同预览组件(加载数据库中的合同模板数据)

  3. 合同项目有一个模块管理页面,可以对多个模板进行维护,比如可以选择启用哪个模板。

  4. 合同的管理员负责维护模板,可以用表单设计器拖拽生成合同模板,提交后落入数据库,每个合同类型可以同时启用一个模板。

  5. 最终下边员工用的就是启用的模板(尤其是这部门卡顿)

下面是电子合同的宏观泳道图:

三、页面介绍

  1. 合同模板管理页

  2. 新增模板页面

  3. 新建合同页面

  4. 合同填写页面

  好了,基本的业务逻辑和页面就介绍这么多,特别卡顿的页面就是第四个页面,下面我们分析一下卡顿的原因。

四、卡顿分析

  1. 首先就是表单设计器的问题最严重,因为每一个组件需要很多配置项才能够支撑组件的渲染,而一个合同是由上千个组件组成,经过测试,一个合同模板需要5MB的存储空间(数据库用的是MongoDB,存储格式为字符串,几乎不影响),下面是一个输入框的配置

  2. 表单设计器的实现用了大量的闭包管理业务,我们都知道,闭包是特别耗内存的。

  3. 合同模板巨复杂,由上万个组件拼接而成,我把模板数据down下来看了一下,大约是16000多个组件,大小为3.4MB。

  4. 因为表单设计器中包括id,model,事件id都是前端随机生成的,采用随机字符串+时间戳的形式,一共46位。

  5. 合同项目属于大型项目,业务场景及其复杂,包括合同管理,附件管理,合同列表,新增页面,审批页面等等,我计算了一下,光路由页面就有三十多个,页面,组件,样式,业务巨多,如果不做处理,不卡才怪

五、性能优化

1. 第一次尝试

  说一下我的优化思路:首先,电子合同由表单设计器和合同业务两个项目共同完成,合同模板加载慢的原因是浏览器渲染了大量的模板数据,这些模板数据是由多个组组成的(大约12个),我第一想到的就是分组渲染,先加载一个组,先让用户看到页面,然后在继续加载,一个一个,最终加载完成。这也是被大家认可的方案。

  然后我就开始实现这个分组渲染,做了大概有二十多天吧,一点效果没出来。

  先看一下渲染的代码:

<template v-show="itemManage==='group'">
<preview-item-template v-for="(item) in domainNodeList"
:key="item.id"
:formNode="item"
:parent="domainNodeList">
</preview-item-template>
</template>

  上面就是所有组加载的代码,这是一个v-for,做分组渲染,我想到使用vue的异步组件实现,但是这是一个循环,所有的组件注册的都是同一个名字,这显然是不能用异步组件的,除非注册的是不同名字的组件,但是我想了很长时间都做出来效果,所以这二十多天,失败了。

2.第二次尝试

  上边说了,模板加载慢是因为浏览器渲染了大量的数据,我们知道,js是单线程的,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。因此js处理数据的能力有限,所以在朋友的建议下调研了一把webworker

  webworker的作用,就是为js创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。在主线程运行的同时,Worker线程在后台运行,两者互不干扰。

  看了一把文档我第一时间觉得这个方案不可行。说到底我们就是想要webworker为我们开辟县城用来处理大量数据,但是webworker处理的大数据,不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。而我们的合同模板数据恰恰是数据量大,并不需要做特别大的运算。

  第二次尝试失败。

3.第三次尝试

  后来在同事的建议下决定采用ssr,也就是服务端预渲染。我们平常写的vue项目打包后生成dist,运维会把这个文件夹放在服务器中,我们看到的页面其实就是生成执行的render函数,这是比较耗时的。

  所谓的服务端渲染,就是在服务端生成静态页面,然后交给客户端渲染。

  自己从零搭建一套服务端渲染的应用是相当复杂的,所以我最终选用了nuxt框架。关于nuxt框架我不多做介绍,可以自己去看文档(传送门)。这个框架有自己的脚手架,也是vue官方推荐的。

  经过了一周的时间,完成了从vue向nuxt的迁移,大部门页面速度有了明显的提升。

  除了我们想优化的新增合同页面。

  经过分析,合同项目用到的组件库有element-UI和我问自己的表单设计器,element只有部门组件支持ssr,像是表格和树是不支持ssr的,所以就不存在服务端渲染了。

  我也曾尝试过弄一把表单设计器,让它支持ssr,但是并没有效果,如果有谁知道,可以联系我。

  很显然,第三次也失败了。

4.第四次尝试

  命运总是很捉弄人,优化了一个多月的合同,速度并没有显著的提升,领导很着急,我也很着急。

  突然有一天,我在回家的途中,记得那天风雨交加,雷霆大作,一声巨雷轰天响,把我好的idea都劈出来了。我一下子想到了分组加载的实现。

先来看一把代码的实现(只展示了部分代码):

<template>
<div class="dialog-preview" v-show="!formLoading">
<el-form ref="previewForm" onsubmit="return false"
:size="formSettingState.componentSize"
@hook:mounted="formMounted"
:model="formModels"> <template v-show="itemManage==='group'">
<preview-item-template v-for="(item) in cutDomainNodeList.one"
:key="item.id"
:formNode="item"
:parent="cutDomainNodeList.one">
</preview-item-template>
</template>
<template v-if="itemManage==='group' && formLoadingTwo">
<preview-item-template v-for="(item) in cutDomainNodeList.two"
:key="item.id"
:formNode="item"
:parent="cutDomainNodeList.two">
</preview-item-template>
</template>
<template v-if="itemManage==='group' && formLoadingThree">
<preview-item-template v-for="(item) in cutDomainNodeList.three"
:key="item.id"
:formNode="item"
:parent="cutDomainNodeList.three">
</preview-item-template>
</template>
</template>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
formLoading: true,
formLoadingTwo: false,
formLoadingThree: false
}
},
computed: {
cutDomainNodeList () {
let { domainNodeList } = this;
let length = domainNodeList.length;
if ( length <= 4 ) {
return {
one: domainNodeList
}
}else {
return {
one: domainNodeList.filter((el, index) => index <=2 ),
two: domainNodeList.filter((el, index) => index>2 && index <=5 ),
three: domainNodeList.filter((el, index ) => index > 5)
}
}
},
methods: {
formMounted () {
setTimeout(() => { this.formLoading = false }, 500);
setTimeout(() => { this.formLoadingTwo = true }, 700);
setTimeout(() => { this.formLoadingThree = true}, 900);
}
}
}

分块加载实现思路:

   1. 首先我把模板数据这个list利用计算属性先做了个判断,如果数组长度小于4,证明数据量较小,不需要分块加载,如果大于4证明数据量大,需要进行分块加载

   2. 分块加载是根据数组索引过滤的,第一块是0-2组,第二块是2-5组,第三块是索引大于5的(也可以分割的跟细),然后再页面中分别遍历渲染

   3. 看一下html中的el-form这个标签,里边有个@hook:mounted="formMounted"这句话,@hook:+生命周期代表在这个生命周期时执行,我们等mounted执行完延时500mm开始加载第一块,700mm加载第二块,900毫秒加载第三块,这样分块加载的效果就出来了。

六、其他方面优化

   首先添加了骨架屏组件,让用户在等待的时候能看到过渡效果。

   上面提到,合同模板大约在3.4MB,这个就是个纯json,让浏览器一下子加载这个么大的数据难免卡顿,所以我就在想能不能优化一下模板大小,从而能够提升加载速度。

   表单设计器中包括id,model,事件id都是前端随机生成的,采用随机字符串+时间戳的形式,一共46位,一个英文字符就是一个字节,这就是46个字节,所以我们可以缩短一下随机数的长度,从而减少一下模板大小。

   最终选用了26位随机数,我算了一下,大约能减少一半大小。

   后来我们让测试人员新生成了一个模板,果然,新模板大小1.44MB,缩短了一倍还多。

   其他方面,我们知道表单设计器有些配置做的不到位,所以管理员不得不换个别的方式拖拽模板,所以我们加了一些配置项,从而使管理员可以少拖拽一些组件。这部分优化下来,模板大小大约减少了300多kb.

   我们还可以优化一下表单设计器的代码,把闭包换个实现方式,应该也能提高加载速度,后续会做这些。

   合同业务项目也优化了一些接口,代码,前后端交互方式,以及页面的交互方式提高了性能和视觉效果。

七、总结

   这是我第一次费这么大劲做vue项目的性能优化,虽然坎坷,但也留下了好结果,我们从最初加载需要50秒甚至一分钟,到现在10秒左右就能加载成功,速度提高可近5倍。

   今日成果,虽数月,但众人拾柴,得以燎原,此非一人之功,谢而不及。

vue大型项目高性能优化----想说爱你真的不容易的更多相关文章

  1. VUE 前端项目优化方法

    前端项目通过webpack打包会生成app.js和vendor.js,如果第三方组件依赖过多,会造成打包后的vendor.js过大,页面首次加载的时候会出现白屏时间过长,影响用户体验.对此,我们需要通 ...

  2. vue+webpack+element-ui项目打包优化速度与app.js、vendor.js打包后文件过大

    从开通博客到现在也没写什么东西,最近几天一直在研究vue+webpack+element-ui项目打包速度优化,想把这几天的成果记录下来,可能对前端牛人来说我这技术比较菜,但还是希望给有需要的朋友提供 ...

  3. 优化vue+springboot项目页面响应时间:waiting(TTFB) 及content Download

    优化vue+springboot项目页面响应时间:waiting(TTFB) 及content Download TTFB全称Time To First Byte,是指网络请求被发起到从服务器接收到地 ...

  4. 如何对vue项目进行优化,加快首页加载速度

    上个月上线了一个vue小项目,刚做完项目,打包上线之后,传到服务器上发现首页加载巨慢. 由于开发时间比较紧,我想着怎么快怎么来,因而在开发过程中没考虑过优化性能问题,酿成最后在带宽5M的情况下页面加载 ...

  5. 【Vuejs】335-(超全) Vue 项目性能优化实践指南

    点击上方"前端自习课"关注,学习起来~ 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 D ...

  6. vue前端项目优化策略

    vue前端项目有什么优化策略? .生成打包报告.(可以发现一些问题,并进行解决)2.使用第三方库启用CDN加载3.使用Element-ui的话,按需加载组件4.使用路由懒加载 生成打包报告: .生成打 ...

  7. 在大型项目上,Python 是个烂语言吗

    Robert Love, Google Software Engineer and Manager on Web Search. Upvoted by Kah Seng Tay, I was the ...

  8. 一步步从零开始用 webpack 搭建一个大型项目

    开篇 很多人都或多或少使用过 webpack,但是很少有人能够系统的学习 webpack 配置,遇到错误的时候就会一脸懵,不知道从哪查起?性能优化时也不知道能做什么,网上的优化教程是不是符合自己的项目 ...

  9. 废弃fastjson!大型项目迁移Gson保姆级攻略

    前言 大家好,又双叒叕见面了,我是天天放大家鸽子的蛮三刀. 在被大家取关之前,我立下一个"远大的理想",一定要在这周更新文章.现在看来,flag有用了... 本篇文章是我这一个多月 ...

随机推荐

  1. Scala集合中的函数(扩展)

    1.拉链(zip) 将两个集合进行 对偶元组合并,可以使用拉链 object Demo_031 { def main(args: Array[String]): Unit = { val list1 ...

  2. J20航模遥控器开源项目系列教程(一)制作教程 | 基础版V1.0发布,从0到1

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/C ...

  3. golang time包

    1.时间类型 time.Time类型表示时间. func demo() { now := time.Now() //获取当前时间 fmt.Printf("Now:%v\n", no ...

  4. springMVC入门(六)------json交互与RESTFul风格支持

    简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.由于其简单易用,目前常用来通过AJAX与后台进行交互.springMVC对于接收.发送JSON数据也 ...

  5. kvm 虚拟机中鼠标不同步的问题解决方法

    在<devices>标签下添加 <input type='tablet' bus='usb'/>    

  6. 0基础算法基础学算法 第八弹 递归进阶,dfs第一讲

    最近很有一段时间没有更新了,主要是因为我要去参加一个重要的考试----小升初!作为一个武汉的兢兢业业的小学生当然要去试一试我们那里最好的几个学校的考试了,总之因为很多的原因放了好久的鸽子,不过从今天开 ...

  7. [转] Linux操作系统启动流程

    总启动流程 这部分转自:https://www.cnblogs.com/liang-io/p/9651656.html 一般来说,所有的操作系统的启动流程基本就是: 总的来说,linux系统启动流程可 ...

  8. docker 创建mysql和redis

    1      镜像加速 创建docker 目录 sudo mkdir -p /etc/docker 镜像加速: sudo tee /etc/docker/daemon.json <<-'E ...

  9. 单元测试利器Mockito框架

    什么是Mock Mock 的中文译为仿制的,模拟的,虚假的.对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去. Mock 测试就是在测试过程中,对于某些 不容易构造(如 Ht ...

  10. 初学WebGL引擎-BabylonJS:第2篇-基础模型体验

    此次学习进度会比之前快很多,有了合适的学习方法后也就会有更多的乐趣产生了. 接上一章代码 上章代码 <!DOCTYPE html> <html> <head> &l ...