The Keep-alive component is a Vue provided component that caches component instances and in some cases avoids component mounting and unmounting, which is useful in some scenarios.

For example, we recently encountered a scenario where a component uploading a large file was a time-consuming operation, and if it switched to another page while uploading, the component would be uninstalled and the corresponding download would be cancelled. At this point, you can wrap the component with the keep-alive component. When you switch to another page, the component can still continue to upload the file and switch back to see the upload progress.

keep-alive

Render child node
const KeepAliveImpl: ComponentOptions = { name: `KeepAlive`, setup(props: KeepAliveProps, { slots }: SetupContext) {VNode let current: VNode | null = null return () = > {/ / for children, because there can be Keep alive - a child node, Directly fetch the first child node const children = slots. The default () const rawVNode = children [0] / / tags | ShapeFlags.COM PONENT_SHOULD_KEEP_ALIVE, This component is a 'keep-alive' component. This flag does not follow unmount logic. Because to be cached vnode. ShapeFlag | = ShapeFlags.COM PONENT_SHOULD_KEEP_ALIVE / / record the current child nodes current = vnode / / return to child nodes, Return rawVNode}}}Copy the code

The component’s setup return function, which is the component’s rendering function; Keep-alive is a virtual node that does not need to render, but only needs to render the child node, so the function only needs to return the child node VNode.

Caching function
  • Defines the object that stores cached dataMap, all the cache key value arrayKeysRepresents the cache key value of the current child componentpendingCacheKey;
const cache = new Map()
const keys: Keys = new Set()
let pendingCacheKey: CacheKey | null = null
Copy the code
  • Gets subtree nodes in the render functionVNodethekeyThe cachecacheTo check whether there arekeyCorresponding cache node
const key = vnode.key
const cachedVNode = cache.get(key)
Copy the code

The key is added to the render function that generates the child node, normally 0,1,2… These numbers.

  • Write down the pre-pointkey
pendingCacheKey = key
Copy the code
  • If anyone found the cachecachedVNodeThe node will be cachedcachedVNodeThe component instances and node elements of the node are copied to newVNodeNode. If not found, the current subtree node is selected firstVNodethependingCacheKeyTo join theKeysIn the.
If (cachedVNode) {/ / copy node vnode. El = cachedVNode. El ponent ponent = cachedVNode.com/vnode.com/tags | ShapeFlags.COM PONENT_KEPT_ALIVE, This component is a reusable 'VNode', This tag does not walk the mount logic vnode. ShapeFlag | = ShapeFlags.COM PONENT_KEPT_ALIVE} else {/ / add pendingCacheKey keys. The add (key)}Copy the code

PendingCacheKey: vnode}; Answer: this logic can actually be added here, but the official interval logic is delayed implementation, I think there is no difference.

  • Mount in componentonMountedAnd updateonUpdatedTo add/update the cache
onMounted(cacheSubtree) onUpdated(cacheSubtree) const cacheSubtree = () => { if (pendingCacheKey ! = null) {// Add/update cache cache.set(pendingCacheKey, instance.subtree)}}Copy the code
All the code
const KeepAliveImpl: ComponentOptions = { name: `KeepAlive`, setup(props: KeepAliveProps, { slots }: SetupContext) {let current: VNode | null = null / / some of the data cache const cache = new Map () const keys: Keys = new Set() let pendingCacheKey: CacheKey | null = null/add/update/cache data const cacheSubtree = () = > {the if (pendingCacheKey! = null) {// Add/update cache cache.set(pendingCacheKey, OnMounted (cacheSubtree) onUpdated(cacheSubtree) return () => {const children = Slot.default () const rawVNode = children[0] const key = rawvNode. key const cachedVNode = cache.get(key) PendingCacheKey = key if (cachedVNode) {// Reuse DOM and component instances rawvNode. el = cachedvNode. el rawvnode.component. ponent = CachedVNode.com ponent} else {/ / add pendingCacheKey keys. The add (key)} rawVNode. ShapeFlag | = ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE current = rawVNode return rawVNode } } }Copy the code

At this point, DOM and component instances are cached through the cache.

keep-alivethepatchReuse logic

We know that the GENERATION of VNode is followed by patch logic to generate DOM.

const processComponent = ( n1: VNode | null, n2: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null, optimized: boolean ) => { n2.slotScopeIds = slotScopeIds if (n1 == null) { if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctx as KeepAliveContext).activate( n2, container, anchor, isSVG, optimized ) } else { mountComponent( n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized ) } } }Copy the code

ProcessComponent uses ShapeFlags.COMPONENT_KEPT_ALIVE to activate the parent keep-alive.

const unmount: UnmountFn = ( vnode, parentComponent, parentSuspense, doRemove = false, optimized = false ) => { const { type, props, ref, children, dynamicChildren, shapeFlag, patchFlag, dirs } = vnode if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) { ; (parentComponent! .ctx as KeepAliveContext).deactivate(vnode) return } }Copy the code

Unmount When ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE is unmounted, the deactivate method of the parent keep-alive is called.

Summary: The reuse and unloading of the keep-alive component is taken over by the activate and deactivate methods.

activelogic
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { const instance = vnode.component! DOM Move (vnode, container, Anchor, MoveType.ENTER, parentSuspense) Update prop Patch (instance.vnode, vnode, Container, Anchor, Instance, parentSuspense, isSVG, vNode. slotScopeIds, optimized ) // 3. QueuePostRenderEffect (() => {instance.isDeactivated = false if (instance.a) { invokeArrayFns(instance.a) } const vnodeHook = vnode.props && vnode.props.onVnodeMounted if (vnodeHook) { invokeVNodeHook(vnodeHook, instance.parent, vnode) } }, parentSuspense) }Copy the code
  1. Mount the DOM directly
  2. Update the prop
  3. Asynchronous executiononVnodeMountedHook function
deactivatelogic
const storageContainer = createElement('div') sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! Move (vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) // 3. QueuePostRenderEffect (() => {if (instance.da) {invokeArrayFns(instance.da)} const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted if (vnodeHook) { invokeVNodeHook(vnodeHook, instance.parent, vnode) } instance.isDeactivated = true }, parentSuspense) if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { // Update components tree devtoolsComponentAdded(instance) } }Copy the code
  1. Remove the DOM and mount it under a new div
  2. Asynchronous executiononVnodeUnmountedHook function

Question: Who will deactivate the old node first or deactivate the new node first? Answer: Deactivate the old node first and deactivate the new node second.

keep-alivetheunmountlogic
  • willcacheUnmount all vNodes except the current subtree, and cancel the current componentkeep-aliveSo that the current subtree VNode will followkeep-aliveUninstall and uninstall.
onBeforeUnmount(() => { cache.forEach(cached => { const { subTree, Suspense} = instance const vnode = getInnerChild(subTree) if (cached. Type === vnode.type) {{keep-alive = keep-alive Unmout resetShapeFlag(vnode) // Invoke its deactivated hook here const da = vnode.component! Da da && queuePostRenderEffect(da, suspense) return} // For each cached VNode, unmount(cached)})}) <! Unmount --> function unmount(vnode: VNode) {// Unmout resetShapeFlag(VNode) // unmout _unmount(VNode, instance, parentSuspense)}Copy the code

Keep-alive is unloaded, and its cached DOM will also be unloaded.

keep-aliveConfiguration of cacheinclude.excludeandmax

This part is good to know the logic, do not do the code analysis.

  1. Component name inincludeComponents are cached in;
  2. Component name inexcludeComponents are not cached.
  3. Specify the maximum number of caches, and remove the top of the cache if it is exceeded.

Dynamic components

Method of use
<keep-alive>
  <component is="A"></component>
</keep-alive>
Copy the code
Rendering function
resolveDynamicComponent("A")
Copy the code
resolveDynamicComponentThe logic of the
export function resolveDynamicComponent(component: unknown): VNodeTypes {
  if (isString(component)) {
    return resolveAsset(COMPONENTS, component, false) || component
  }
}

function resolveAsset(
  type,
  name,
  warnMissing = true,
  maybeSelfReference = false
) {
  const res =
    // local registration
    // check instance[type] first which is resolved for options API
    resolve(instance[type] || Component[type], name) ||
    // global registration
    resolve(instance.appContext[type], name)
  return res
}

Copy the code

Like directives, resolveDynamicComponent finds locally or globally registered components by name and renders the corresponding components.