跳至主要內容

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;
}