19. 实现 KeepAlive
19. 实现 KeepAlive
1.基本使用
// 1.组件
const My1 = {
name: 'My1',
setup() {
onMounted(() => {
console.log('my1 mounted');
});
return () => h('h1', 'my1');
}
};
// 2.组件
const My2 = {
name: 'My2',
setup() {
onMounted(() => {
console.log('my2 mounted');
});
return () => h('h1', 'my2');
}
};
// keepAlive会对渲染的组件进行缓存
render(
h(KeepAlive, null, {
default: () => h(My1) // 缓存1
}),
app
);
setTimeout(() => {
render(
h(KeepAlive, null, {
default: () => h(My2) // 缓存2
}),
app
);
}, 1000);
setTimeout(() => {
render(
h(KeepAlive, null, {
default: () => h(My1) // 复用1
}),
app
);
}, 2000);
创建上下文对象,存储
keepAlive
组件渲染时所需的属性
const instance = {
// 组件的实例
ctx: {} // instance上下文
};
export const KeepAliveImpl = {
// keepAlive本身没有任何功能
__isKeepAlive: true,
setup(props, { slots }) {
return () => {
let vnode = slots.default();
return vnode; // 渲染插槽的内容
};
}
};
export const isKeepAlive = (vnode) => vnode.type.__isKeepAlive;
const updateComponentPreRender = (instance, next) => {
instance.next = null;
instance.vnode = next;
updataProps(instance, instance.props, next.props || {});
Object.assign(instance.slots, next.children); // 渲染的时候需要更新插槽
};
const mountComponent = (vnode, container, anchor, parentComponent) => {
// 1) 要创造一个组件的实例
let instance = (vnode.component = createComponentInstance(
vnode,
parentComponent
));
if (isKeepAlive(vnode)) {
(instance.ctx as any).renderer = {
patch,
createElement: hostCreateElement,
move(vnode, container) {
hostInsert(vnode.component.subTree.el, container);
},
unmount
};
}
};
2.缓存组件
在渲染完毕后需要对`subTree`进行缓存,需要保证渲染完毕后在调用`mounted`事件
export const KeepAliveImpl = {
__isKeepAlive: true,
setup(props, { slots }) {
const keys = new Set(); // 缓存的key
const cache = new Map(); // 缓存key对应的虚拟节点
const instance = getCurrentInstance();
let pendingCacheKey = null;
onMounted(() => {
cache.set(pendingCacheKey, instance.subTree);
});
return () => {
let vnode = slots.default();
// 如果
if (
!isVnode(vnode) ||
!(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)
) {
return vnode;
}
const comp = vnode.type; // 拿到组件
// 获取组件的key
const key = vnode.key == null ? comp : vnode.key;
const cacheVNode = cache.get(key);
pendingCacheKey = key;
if (cacheVNode) {
} else {
keys.add(key);
}
// 标识组件
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
return vnode;
};
},
};
3.复用组件
export const KeepAliveImpl = {
__isKeepAlive: true,
setup(props, { slots }) {
// ...
let { createElement, move, unmount: _unmount } = instance.ctx.renderer;
const storageContainer = createElement('div'); // 缓存盒子
instance.ctx.activate = (vnode, container, anchor) => {
// 激活则移动到容器中
move(vnode, container, anchor);
};
instance.ctx.deactivate = (vnode) => {
// 卸载则移动到缓存盒子中
move(vnode, storageContainer, null);
};
return () => {
// ...
if (cacheVNode) {
// 缓存中有
vnode.component = cacheVNode.component; // 复用组件,并且标识不需要真正的创建
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
// make this key the freshest
keys.delete(key);
keys.add(key);
}
};
}
};
let {
h,
render,
reactive,
provide,
inject,
Teleport,
defineAsyncComponent,
KeepAlive,
ref
} = VueRuntimeDOM;
// keepAlive会对渲染的组件进行缓存
const state = ref(true);
const My1 = {
render: () => h('h1', 'hello')
};
const My2 = {
render: () => h('h1', 'world')
};
render(
h(KeepAlive, null, {
// 渲染My1
default: () => h(My1)
}),
app
);
setTimeout(() => {
render(
h(KeepAlive, null, {
// 渲染My2
default: () => h(My2)
}),
app
);
}, 1000);
setTimeout(() => {
render(
h(KeepAlive, null, {
// 在渲染My1
default: () => h(My1)
}),
app
);
}, 2000);
卸载组件
const unmount = (vnode, parentComponent) => {
const { shapeFlag } = vnode;
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
parentComponent.ctx.deactivate(vnode);
return;
}
};
挂载组件
const processComponent = (n1, n2, container, anchor, parentComponent) => {
// 统一处理组件, 里面在区分是普通的还是 函数式组件
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
parentComponent.ctx.activate(n2, container, anchor);
} else {
mountComponent(n2, container, anchor, parentComponent);
}
} else {
// 组件更新靠的是props
updateComponent(n1, n2);
}
};
const cacheSubtree = () => {
cache.set(pendingCacheKey, instance.subTree);
};
onMounted(cacheSubtree);
onUpdated(cacheSubtree); // 在更新时进行重新缓存
4.max 控制缓存
function unmount(vnode) {
resetShapeFlag(vnode);
_unmount(vnode, instance);
}
function pruneCacheEntry(key) {
const cached = cache.get(key);
unmount(cached);
cache.delete(key);
keys.delete(key);
}
if (cacheVNode) {
// 缓存中有
vnode.component = cacheVNode.component; // 复用组件,并且标识不需要真正的创建
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
} else {
keys.add(key);
if (max && keys.size > max) {
// 超过限制删除第一个
pruneCacheEntry(keys.values().next().value);
}
}
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
function resetShapeFlag(vnode) {
let shapeFlag = vnode.shapeFlag;
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
shapeFlag -= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
}
if (shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
shapeFlag -= ShapeFlags.COMPONENT_KEPT_ALIVE;
}
vnode.shapeFlag = shapeFlag;
}