永远不要过早优化,要做到因地制宜,见招拆招。
使用key
对于通过循环生成了列表,应该给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除、新增、改动元素。
使用冻结对象
冻结的对象不会被响应化。有的时候没有必要将数据变成响应式的数据,因为在数据的响应化的过程中需要递归遍历数据,需要耗时。
JavaScript
Object.freeze(this.data);
使用函数式组件
参见函数式组件
使用函数式组件,在js执行时间以及渲染时间上稍有减少,但是差别不大,同时在内存占用(消耗)方面会比普通组件占用少,这是因为使用函数式组件不会在vue的组件树中生成该组件,不会为函数组件创建实例,只是纯渲染。对于普通组件,vue内部会通过new VueComponent()对每一个组件创建新的组件实例
使用计算属性
如果模板中某个数据会使用多次,并且该数据是通过计算得到的,使用计算属性以缓存它们
非实时绑定的表单项
当使用v-model
绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue
发生重渲染(rerender
),这会带来一些性能的开销。
特别是当用户改变表单项时,页面有一些动画正在进行中,由于JS执行线程和浏览器渲染线程是互斥的,最终会导致动画出现卡顿。
我们可以通过使用lazy
或不使用v-model
的方式解决该问题,但要注意,这样可能会导致在某一个时间段内数据和表单项的值是不一致的。
保持对象引用稳定
在绝大部分情况下,vue
触发rerender
的时机是其依赖的数据发生变化 ,若数据没有发生变化,哪怕给数据重新赋值了,vue
也是不会做出任何处理的,下面是vue
判断数据没有变化的源码
JavaScript
export function hasChanged(x:unknow,y:unknown):boolean {
if(x === y) {
return x === 0 && 1/x !=== 1 / (y as number) // +0 === -0 --> true
} else {
return x === x || y === y // NaN !== NaN --> true
}
}
因此,如果需要,只要能保证组件的依赖数据不发生变化,组件就不会重新渲染。
对于原始数据类型,保持其值不变即可
对于对象类型,保持其引用不变即可
从另一方面来说,由于可以通过保持属性引用稳定来避免子组件的重渲染,那么我们应该细分组件来尽量避免多余的渲染
使用v-show替代v-if
对于频繁切换显示状态的元素,使用v-show可以保证虚拟dom树的稳定,避免频繁的新增和删除元素,特别是对于那些内部包含大量dom元素的节点,这一点极其重要
关键字:频繁切换显示状态、内部包含大量dom元素
使用延迟装载(defer)
首页白屏时间主要受到两个因素的影响:
-
打包体积过大
巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个
<div>
,没有可显示的内容 -
需要立即渲染的内容太多
JS传输完成后,浏览器开始执行JS构造页面。
但可能一开始要渲染的组件太多,不仅JS执行的时间很长,而且执行完后浏览器要渲染的元素过多,从而导致页面白屏
一个可行的办法就是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来
延迟装载是一个思路,本质是利用requestAnimationFrame事件分批渲染内容,它的具体实现多种多样。
使用keep-alive
keep-alive组件是vue的内置组件,用于缓存内部组件实例。被包裹的组件在切换的时候不会被销毁,直接使用缓存中的实例,好处是避免创建组件带来的开销、而且可以保留组件的状态,但是会因此占用更多的内存。
属性
- include 哪些组件缓存
- exclude 哪些组件不缓存
- max 最大缓存数,超出vue会自动移除最久没有使用的组件缓存
vue
// 写法1
<keep-alive :include="['Comp1','Comp2']">
<component :is="comps[curIndex]">
</keep-alive>
// 写法2
<keep-alive include="Comp1,Comp2" :max="2">
<component :is="comps[curIndex]">
</keep-alive>
关于max属性,vue是这么判断的
js
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
cache[keyToCache] = {
name: _getComponentName(componentOptions),
tag,
componentInstance
}
keys.push(keyToCache)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode) // 移除keys中的第一个
}
this.vnodeToCache = null
}
}
特有的生命周期
- activated 组件激活时调用,第一次调用是在mounted之后调用
- deactivated 组件失活时调用
常见的应用场景
后台管理系统的选项卡,当菜单添加到选项卡时,就对选项卡中的菜单进行缓存,提高用户的使用体验。
原理:
keep-alive在内部维护一个keys数组和一个缓存对象cache
js
created() {
this.cache = Object.create(null)
this.keys = []
},
keys数组记录缓存组件的key值,如果组件没有,就会为组件自动生成一个唯一的key值
js
const { cache, keys } = this
const key =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
cache以key值为键,以vnode为值,用于缓存组件对应的虚拟DOM
js
keys:[1,2,3]
cache:{
1:vnode1,
2,vnode2,
3:vnode3
}
大致流程:
js
render() {
const slot = this.$slots.default // 获取默认插槽
const vnode = getFirstComponentChild(slot) // 得到插槽中第一个组件的vnode
const componentOptions = vnode && vnode.componentOptions // 获取vnode的信息
if (componentOptions) {
// check pattern
const name = _getComponentName(componentOptions) // 获取组件名字
const { include, exclude } = this
// 判断是否需要缓存
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
// 来到这里,说明组件需要缓存,有缓存复用组件实例,无缓存则进行缓存
const { cache, keys } = this
// 获取组件的key
const key =
vnode.key == null
? componentOptions.Ctor.cid +
(componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
} else {
// 无缓存,进行缓存
this.vnodeToCache = vnode
this.keyToCache = key
}
// @ts-expect-error can vnode.data can be undefined
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}