<keep-alive>是Vue.js的一个内置组件,可以使被包罗的组件保存状态或制止重新渲染。下面来分析源码runtime-core/src/components/KeepAlive.ts的实现原理。
在setup方法中会创建一个缓存容器和缓存的key列表,其代码如下:
setup(props: KeepAliveProps, { slots }: SetupContext) {// keep-alive组件的上下文对象 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.ctx as KeepAliveContext // 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 } }/* 缓存对象 */ const cache: Cache = new Map() const keys: Keys = new Set() // 替换内容 sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { const instance = vnode.component! move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // 处置惩罚props改变 patch( ... ) ... } // 替换内容 sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) ... } } <keep-alive>本身实现了render方法,并没有使用Vue内置的render方法(颠末<template>内容提取、转换AST、render字符串等一系列过程),在实行<keep-alive>组件渲染时,就会实行这个render方法:
return () => { pendingCacheKey = null if (!slots.default) { return null }// 得到插槽中的第一个组件 const children = slots.default() const rawVNode = children[0] if (children.length > 1) { if (__DEV__) { warn(`KeepAlive should contain exactly one component child.`) } current = null return children } else if ( !isVNode(rawVNode) || (!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) && !(rawVNode.shapeFlag & ShapeFlags.SUSPENSE)) ) { current = null return rawVNode } let vnode = getInnerChild(rawVNode) const comp = vnode.type as ConcreteComponent // for async components, name check should be based in its loaded // inner component if available // 获取组件名称,优先获取组件的name字段 const name = getComponentName( isAsyncWrapper(vnode) ? (vnode.type as ComponentOptions).__asyncResolved || {} : comp ) // name不在include中大概exclude中,则直接返回vnode(没有存取缓存) const { include, exclude, max } = props if ( (include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name)) ) { current = vnode return rawVNode } const key = vnode.key == null ? comp : vnode.key const cachedVNode = cache.get(key) // 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 // 假如已经缓存了,则直接从缓存中获取组件实例给vnode,若还未缓存,则先辈行缓存 if (cachedVNode) { // copy over mounted state vnode.el = cachedVNode.el vnode.component = cachedVNode.component // 实行transition if (vnode.transition) { // recursively update transition hooks on subTree setTransitionHooks(vnode, vnode.transition!) }// 设置shapeFlag标志位,为了制止实行组件mounted方法 // avoid vnode being mounted as fresh vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE // make this key the freshest // 重新设置一下key包管最新 keys.delete(key) keys.add(key) } else { keys.add(key) // prune oldest entry // 当超出max值时,扫除缓存 if (max && keys.size > parseInt(max as string, 10)) { pruneCacheEntry(keys.values().next().value) } } // avoid vnode being unmounted vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE current = vnode return isSuspense(rawVNode.type) ? rawVNode : vnode } 在上面的代码中,当缓存的个数凌驾max(默认值为10)的值时,就会扫除旧的数据,这其中就包罗<keep-alive>的缓存更新计谋,其依照了LRU(Least Rencently Used)算法。
1. LRU算法