概念
keep-alive
是Vue的一个内置实例,用于缓存组件。当keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是摧毁它们。keep-alive
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链之中。如需要在两个组件之间来回切换,或者从一个分类页跳转到详情页,之后再返回分类页,仍然可以保持上次的状态,又或者在浏览首页时重新切换回来,滚动条会保持在上次位置(等等),这时候我们就可以用到keep-alive
组件。
作用
-
在组件切换的过程中将状态保留在内存。
*防止重复渲染DOM,减少加载时间以及性能的消耗,提高用户的体验感。
Props(参数)
========= -
include
-string | RegExp | Array
。只有名称匹配的组件会被缓存。 -
exclude
-string | RegExp | Array
。任何名称匹配的组件都不会被缓存。 -
max
-number | string
。最多可以缓存多少组件实例。
生命周期函数
activated : 在组件激活的时候使用,第一次激活是在mounted之后执行。
deactivated : 在失效的时候使用。
注意 :
- 被包含在keep-alive中创建的组件,会多出两个生命周期的钩子:activated和deactivated
- 使用keep-alive会将数据保留在内存中,如果要在每次进入页面的时候获取最新数据,需要在activated阶段获取数据,承担原来created钩子函数中获取数据的任务。
用法
keep-alive
的使用只需要在动态组件的最外层添加标签即可。
在动态组件中的应用
<!-- 基本 -->
<keep-alive><component :is="choose"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive><comp-a v-if="a > 1"></comp-a><comp-b v-else></comp-b>
</keep-alive>
在vue-router中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
实现原理
步骤解析
通过 keep-alive
组件插槽,获取第一个子节点。根据 include
、exclude
判断是否需要缓存,通过组件的 key
,判断是否命中缓存。利用 LRU
算法,更新缓存以及对应的 keys
数组。根据 max
控制缓存的最大组件数量。
源码分析
基本结构
export declare const KeepAlive: {new (): {$props: VNodeProps & KeepAliveProps;};__isKeepAlive: true;
};
export declare type VNodeProps = {key?: string | number | symbol;ref?: VNodeRef;ref_for?: boolean;ref_key?: string;onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[];onVnodeMounted?: VNodeMountHook | VNodeMountHook[];onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[];onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[];onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[];onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[];
};
export declare interface KeepAliveProps {include?: MatchPattern;exclude?: MatchPattern;max?: number | string;
}
declare type MatchPattern = string | RegExp | (string | RegExp)[];
主要流程
在 keep-alive
组件的实现中,使用了 Map
对象和 Set
对象来实现组件的缓存,如下代码所示:
// 创建一个缓存对象
const cache: Cache = new Map()
// 存储组件的 key
const keys: Keys = new Set()
其中 Map
对象的键是组件选项对象,即 vnode.type 属性的值,而该 Map
对象的值是用于描述组件的 vnode
对象。由于用于描述组件的 vnode
对象存在对组件实例的引用 (即 vnode.component属性),所以缓存 vnode
对象,就等价与缓存了组件实例。
Set
对象存储的是组件对象的 key
。Set
对象具有自动去重的功能,因此Set
对象中存储重复的组件对象。
keep-alive
组件中对缓存的管理时,首先会在组件的 onMounted
或 onUpdated
生命周期钩子函数中设置缓存,如下代码所示:
let pendingCacheKey: CacheKey | null = null
const cacheSubtree = () => {if (pendingCacheKey != null) {// 设置缓存cache.set(pendingCacheKey, getInnerChild(instance.subTree))}
}
// 执行生命周期钩子函数
onMounted(cacheSubtree)
onUpdated(cacheSubtree)
然后在keep-alive
组件返回的函数中根据 vnode
对象的 key
去缓存中查找是否有缓存的组件,如果缓存存在,则继承组件实例,并将用于描述组件的 vnode
对象标记为 COMPONENT_KEPT_ALIVE
,这样渲染器就不会重新创建新的组件实例;如果缓存不存在,则将 vnode
对象的key
添加到 keys
集合中,然后判断当缓存数量超过指定阈值时就对缓存进行修剪。如下代码所示:
return () => {// 如果 vnode 上不存在 key,则使用 vnode.type 作为keyconst key = vnode.key == null ? comp : vnode.key// 根据 vnode 的key去缓存中查找是否有缓存的组件const cachedVNode = cache.get(key)if (vnode.el) {vnode = cloneVNode(vnode)if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {rawVNode.ssContent = vnode}}the normalized vnode) inpendingCacheKey = keyif (cachedVNode) {// 如果缓存中存在缓存的组件vnode.el = cachedVNode.el// 继承缓存的组件vnode.component = cachedVNode.component// 如果组件上有动画,处理动画if (vnode.transition) {setTransitionHooks(vnode, vnode.transition!)}vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVEkeys.delete(key)keys.add(key)} else {// 将 vnode 的key 添加到 keys 集合中keys.add(key)// 当缓存数量超过指定阈值时对缓存进行修剪if (max && keys.size > parseInt(max as string, 10)) {pruneCacheEntry(keys.values().next().value)}}
}
Vue.js 中采用的修剪策略叫作 “最新一次访问”,其核心在于,把当前访问 (或渲染) 的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。如下面的代码所示:
function pruneCacheEntry(key: CacheKey) {// 从缓存对象中获取缓存的 VNodeconst cached = cache.get(key) as VNode// 如果没有当前渲染的组件或当前渲染的组件和缓存中的组件不是同一个,卸载缓存中的组件if (!current || cached.type !== current.type) {unmount(cached)} else if (current) {// 当前活动实例不应再保持活动状态。resetShapeFlag(current)}cache.delete(key)keys.delete(key)
}
总结
keep-alive
组件的作用类似于 HTTP 中的持久链接。它可以避免组件实例不断地被销毁和重建。keep-alive
的实现并不复杂。当被 keep-alive
的组件在卸载的时候,渲染器并不会真的将其卸载,而是会将该组件搬运到一个隐藏的容器中,从而使得组件可以维持当前状态。当被 keep-alive
的组件再次被 “挂载” 时,渲染器也不会真的挂载它,而是将它从隐藏容器中搬运到原容器。
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享