// Marker for special handling inside the renderer. We are not using a === // check directly on KeepAlive in the renderer, because importing it directly // would prevent it from being tree-shaken. __isKeepAlive: true,
{ // ... setup(props: KeepAliveProps, { slots }: SetupContext) { const instance = getCurrentInstance()! // KeepAlive communicates with the instantiated renderer via the // ctx where the renderer passes in its internals, // and the KeepAlive instance exposes activate/deactivate implementations. // The whole point of this is to avoid importing KeepAlive directly in the // renderer to facilitate tree-shaking. const sharedContext = instance.ctxasKeepAliveContext
// if the internal renderer is not registered, it indicates that this is server-side rendering, // for KeepAlive, we just need to render its children if (__SSR__ && !sharedContext.renderer) { return() => { const children = slots.default && slots.default() return children && children.length === 1 ? children[0] : children } }
// 组件卸载 functionunmount(vnode: VNode) { // reset the shapeFlag so it can be properly unmounted resetShapeFlag(vnode) _unmount(vnode, instance, parentSuspense, true) }
functionpruneCacheEntry(key: CacheKey) { const cached = cache.get(key) asVNode if (!current || cached.type !== current.type) { unmount(cached) } elseif (current) { // current active instance should no longer be kept-alive. // we can't unmount it now but it might be later, so reset its flag now. resetShapeFlag(current) } cache.delete(key) keys.delete(key) }
// cache sub tree after render letpendingCacheKey: CacheKey | null = null constcacheSubtree = () => { // fix #1621, the pendingCacheKey could be 0 if (pendingCacheKey != null) { cache.set(pendingCacheKey, getInnerChild(instance.subTree)) } } onMounted(cacheSubtree) onUpdated(cacheSubtree)
// 卸载前,在其中调用组件的 onDeactived 钩子 onBeforeUnmount(() => { cache.forEach(cached => { const { subTree, suspense } = instance const vnode = getInnerChild(subTree) if (cached.type === vnode.type) { // current instance will be unmounted as part of keep-alive's unmount resetShapeFlag(vnode) // but invoke its deactivated hook here const da = vnode.component!.da da && queuePostRenderEffect(da, suspense) return } unmount(cached) }) })
// 取默认插槽中的第一个组件 const children = slots.default(); const rawVNode = children[0];
// 如果默认插槽中有多个组件,则直接返回它们,导致无法进入缓存流程 if (children.length > 1) { if (__DEV__) { warn(`KeepAlive should contain exactly one component child.`); } current = null;
// for async components, name check should be based in its loaded // inner component if available const name = getComponentName( isAsyncWrapper(vnode) ? (vnode.typeasComponentOptions).__asyncResolved || {} : comp );
const { include, exclude, max } = props;
// 根据 匹配模式 和 组件名 校验 if ( (include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name)) ) { current = vnode; return rawVNode; }
// clone vnode if it's reused because we are going to mutate it if (vnode.el) { vnode = cloneVNode(vnode); if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) { rawVNode.ssContent = vnode; } } // #1513 it's possible for the returned vnode to be cloned due to attr // fallthrough or scopeId, so the vnode here may not be the final vnode // that is mounted. Instead of caching it directly, we store the pending // key and cache `instance.subTree` (the normalized vnode) in // beforeMount/beforeUpdate hooks. pendingCacheKey = key;
// 存在缓存的值,就 if (cachedVNode) { // copy over mounted state vnode.el = cachedVNode.el; vnode.component = cachedVNode.component; if (vnode.transition) { // recursively update transition hooks on subTree setTransitionHooks(vnode, vnode.transition!); } // avoid vnode being mounted as fresh vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE; // make this key the freshest keys.delete(key); keys.add(key); } else { // 限制最大缓存数量 keys.add(key); // prune oldest entry if (max && keys.size > parseInt(max asstring, 10)) { pruneCacheEntry(keys.values().next().value); } } // avoid vnode being unmounted vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE;
current = vnode; returnisSuspense(rawVNode.type) ? rawVNode : vnode; };
三、KeepAlive
KeepAlive就是KeepAliveImpl,重新声明了类型。
1 2 3 4 5 6 7 8
/ export the publictypefor h/tsx inference // also to avoid inline import() in generated d.ts files exportconstKeepAlive = KeepAliveImplasanyas { __isKeepAlive: true new (): { $props: VNodeProps & KeepAliveProps } }
functionregisterKeepAliveHook( hook: Function & { __wdc?: Function }, type: LifecycleHooks, target: ComponentInternalInstance | null = currentInstance ) { // cache the deactivate branch check wrapper for injected hooks so the same // hook can be properly deduped by the scheduler. "__wdc" stands for "with // deactivation check". const wrappedHook = hook.__wdc || (hook.__wdc = () => { // only fire the hook if the target instance is NOT in a deactivated branch. letcurrent: ComponentInternalInstance | null = target; while (current) { if (current.isDeactivated) { return; } current = current.parent; } returnhook(); }); injectHook(type, wrappedHook, target); // In addition to registering it on the target instance, we walk up the parent // chain and register it on all ancestor instances that are keep-alive roots. // This avoids the need to walk the entire component tree when invoking these // hooks, and more importantly, avoids the need to track child components in // arrays. if (target) { let current = target.parent; while (current && current.parent) { if (isKeepAlive(current.parent.vnode)) { injectToKeepAliveRoot(wrappedHook, type, target, current); } current = current.parent; } } }
// injectHook(type, hook, keepAliveRoot, true /* prepend */) // true 表示把 hook 放到 keepAliveRoot[type] 对应的钩子列表的前面,即使用 unshift() 方法 functioninjectToKeepAliveRoot( hook: Function & { __weh?: Function }, type: LifecycleHooks, target: ComponentInternalInstance, keepAliveRoot: ComponentInternalInstance ) { // injectHook wraps the original for error handling, so make sure to remove // the wrapped version. const injected = injectHook(type, hook, keepAliveRoot, true/* prepend */); // 卸载时移除 onUnmounted(() => { remove(keepAliveRoot[type]!, injected); }, target); }