组件缓存
- 作用:容器组件,无实际意义,被包裹的组件可视为插槽。插槽会被缓存起来,不会重复渲染
- 本质:
- 是一个插槽组件
h(component,null,{default:()=>h(component)}
- 默认取插槽的第一个孩子节点,vue3中规定一个
keep-alive容器只能存在一个孩子default-slot
- 使用示例
<keep-alive :include="cacheList">
<component
:is="Component"
:key="route.path"
class="router-component"
/>
</keep-alive>
const My1 = {
setup(){
onMounted(()=>{
console.log('my1')
})
},
render:h('h1','my1')
}
render(h(KeepAlive,null,{
default:()=>h(My1)
}))
原理简述
- 缓存虚拟节点数据,并创建真实DOM元素碎片保存被切换掉的组件DOM,当切换回来时,从元素中提取出来并放置被切换的组件DOM。
- 通过绑定的
key值与容器渲染的组件绑定
注意:
- slot指的是组件的虚拟节点,并非真实的DOM,每次切换
vnode.component => subTree。
- 只有当容器组件渲染完时,才能确定渲染的结果
subTree。
- 容器初次渲染时在容器组件的
onMounted钩子中将key值与组件绑定。
- 切换时在容器的
onUpdated钩子中将新的key值与组件绑定。
- 组件实例切换时
- 旧组件实例加标识
ShapeFlag.COMPONENT_SHOULD_KEEP_ALIVE,不走卸载,而是直接将实例的子节点subTree移入缓存中Map对象,并触发deactived钩子
- 新组件渲染,并触发actived钩子
- 若新组件实例在缓存对象中,则加标识
ShapeFlag.COMPONENT_KEPT_ALIVE,不走创建,直接从缓存的DOM元素中拿出来
- 若不在缓存对象中,则创建并渲染。
- 属性说明
includes:要缓存的组件名称
excludes:不需要缓存的组件名称
max:最大缓存的组件个数,防止缓存对象过大
- 缓存策略LRU算法:队列中越先进且越久没被反复用到的,优先删除。跟先进先出差不多,不过复用时会将其刷新为最新的。
component.name:声明组件时自定义的名称
- 根据规则字符串匹配、正则匹配等方式,将组件实例的名称与传入的属性做对比,符合的就缓存这也是模板名称的意义,如果不需要缓存则没有意义。
import { isVnode } from "./vnode";
import { getCurrentInstance } from "./component";
import { ShapeFlags } from "@vue/shared";
import { onMount, updated } from "./apiLifecycle";
export const keepAliveImpl = {
__isKeepAlive: true,
props: {
include: {},
exclude: {},
max: {},
},
setup(props, { slots }) {
const keys = new Set();
const cache = new Map();
const instance = getCurrentInstance();
let { createElement, move } = instance.ctx.renderer;
let storageContainer = createElement("div");
instance.ctx.deactivate = function (vnode) {
move(vnode, storageContainer);
};
instance.ctx.activate = function (vnode, container, achor) {
move(vnode, container, achor);
};
let pendingKey = null;
function cacheSubTree() {
if (pendingKey) {
cache.set(pendingKey, instance.subTree);
}
}
onMount(cacheSubTree);
updated(cacheSubTree);
const { include, exclude, max } = props;
let current = null;
return () => {
let vnode = slots.default();
if (
!isVnode(vnode) ||
!(vnode.shapeFlags & ShapeFlags.STATEFUL_COMPONENT)
) {
return;
}
const comp = vnode.type;
const key = vnode.key == null ? comp : vnode.key;
let name = comp.name;
if (
(name && !include.split(",").includes(name)) ||
(exclude && exclude.split(",").includes(name))
) {
return vnode;
}
let cacheVnode = cache.get(key);
if (cacheVnode) {
vnode.component = cacheVnode.component;
vnode.shapeFlags |= ShapeFlags.COMPONENT_KEPT_ALIVE;
} else {
keys.add(key);
pendingKey = key;
if (max && keys.size > max) {
}
vnode.shapeFlags |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
current = vnode;
}
return vnode;
};
},
};