下面按优化效果由高到低分享几个优化技巧,在最下面会有分类速览,也相当于一个总结。
拆分组件
我也曾以为,拆分子组件是用于抽象,但实践告诉我,拆分子组件是提升性能的一种方式(特定情况)。
在我的实际工作中遇到这么个问题,有一个很大的表格,里面有多个新增条目的对话框,当数据很多的时候,在弹框中填写新增数据都会变卡。
原因就是,在一个组件里,修改值会触发整个组件的 render 函数和 diff。但是明知道只是弹框填个数字,表单本身什么都没改,我还要浪费时间检查个啥呢?
为了解决这个问题,把对话框单独抽出来就成了十分有效的优化方法。
因为 Vue 的更新,是以组件为单位的,子组件更新是不会触发父组件更新的,除非子组件改变了父组件的数据。
以 element UI 的 dialog 举例吧(打开此链接直接打开可运行事例)。
写一个页面,里面包含两个 dialog,一个是直接写到页面中,另一个抽象为组件。
先看组件化的 dialog 吧,十分简单:
主页面如下,包含一个 el-dialog 和一个已经抽象的 my-dialog:
实践可知,在 el-dialog 打开关闭、以及输入框修改数据时,会触发整个组件的渲染函数,而在 my-dialog 无论打开关闭或输入时都不会触发父组件更新。在对话框所在组件的数据量少的话确实差别不大,但是量大的时候在对话框输入的时候会有可感知的卡顿。(一句话:对话框自成一个组件,内部更新不影响父组件)
不止如此,反过来说,父组件更新的时候会渲染 el-dialog 而不会渲染 my-dialog,实在是一举两得。(一句话:父组件更新时不改变没有数据变化的子组件)
即使这个组件不复用,也可以把对话框用到的方法分离到单独文件,不用和主页面的方法混到一起。如果一个 dialog 有一大堆逻辑的话,分离到单独文件绝对是一个不错的方法。
不过缺点当然也有:数据交互有点不方便,你可以尝试活用 $refs
、$parent
和 Vuex 等方式解决。
如果你不喜欢我上面使用 this.$refs.myDialog.visible = true;
的方案,也可以用传统 $emit
和 $on
。
关于更新粒度更详细的解释可以看这里:Vue 和 React 对于组件的更新粒度有什么区别
另外你也可以在 React 渲染优化:diff 与 shouldComponentUpdate 中了解到在 React 中如何优化子组件更新的问题。
P.S.另外有一个隐藏结论,一个组件用了 slot 的话,子组件是会随着父组件重新渲染的
替换重型组件
开源轮子造福大家,但是满足的人越多,冗余的逻辑就越多。正常来说,它们确实是满足性能要求的,但是如果你在业务中真的遇到要用 v-for 循环几十上百个表格,那冗余逻辑造成的效率影响不容小觑,也会占用更多的内存。
所以尝试为自己造轮子吧,以最最简单的方式实现你的最小要求。我自己的话就是因为表格太多,然后 element UI 的表格组件太复杂,所以自己写了一个表格组件 cpn-tbl;以及一个仿 vee-validate 的轻量校验插件 v-vld。
在实现组件时,你可能更需要熟悉 render 函数的使用方法,这样就能更自由更简洁地操纵节点结构。
v-if
如果你需要首屏尽量快,v-if
绝对是你的好帮手。
另外,在 v-if
中使用异步组件可以延迟部分代码的加载。
借助 webpack 的代码拆分特性可以比较轻松实现这样的异步组件,当然,代码拆分也可以用于大型 js 文件(一些重型库)的延后加载。
P.S.说到代码拆分,这是一个无论使用什么框架都必须懂的优化方法,尤其是借助 Webpack 你可以借助简单的语法拆分代码,追求首屏的极致速度。
相反,使用 v-show
可以缓存组件增加 toggle 的流畅度。
分片渲染
这是一条尤其针对滚动流畅度的优化。大家都知道浏览器渲染和 JavaScript 运行互斥,如果一段代码在浏览器需要渲染的时候运行超过 1000/30 ms 的话,看起来就不足 30 帧了。
相关传送门:requestAnimationFrame
缓存 ajax 数据
可以封装得像跟普通 axios 的 get 一样,直接替换原来的 axios 对象:
如果实在觉得 response.config.url + (params ? JSON.stringify(params) : '')
这样的 id 太长的话,不妨加一个 hash()
缩短一下,不过会丧失了可读性。
冻结
针对只读表格等不会修改的数据,完全没有必要让数据变得“响应式”。Object.freeze()
就是用于这样的场景,阻止赋值到响应式数据,节省掉递归“响应式”的时间。对于大量而且大深度的对象,节省的时间还是挺可观。
有 for 必 key
作用:为 diff 算法加速。不过记得不要用单纯的 index 作 key,另外,for 一个 template 的话,每一个子元素都需要不同的 key。
说到这里突然想起,在用 element UI 的时候循环渲染 el-form-item,key 写错了,导致了无限重新渲染,debug 了好久 😂
分类速览
组件角度优化
- 拆分组件,利用组件级别的更新粒度优化更新速度
- 慎用重型组件,有必要时自己造,插件同理
- 使用函数式组件(低优先)
处理响应式的副作用
- 利用响应式的反模式
- 减少在依赖收集时使用 this(可优化首屏速度)
降低渲染压力
- v-show 与 v-if 的平衡
- 分片渲染
Vue 自带的缓存
- keepalive
- computed
其他优化
- 数据缓存
- 虚拟滚动
- 去除 console.log