04-vue3中keep-alive 实现原理解析
共 3869字,需浏览 8分钟
·
2021-12-13 12:27
你好我是刘小灰,这是我的第 04 篇原创文章。
前言
最近在做项目中遇到一个很奇葩的问题,就是给路由设置 keep-alive
就是不生效,就在写这篇文章之前,这个 bug 还是没得到解决,所以打算看看 keep-alive
的实现逻辑,看是否从源码中得到些解决这个bug的启发。
怎么去看源码
为了高效,我们不能漫无目的的去看源码,那样会很痛苦且没有任何收获,所以我建议大家在看源码的时候最好是带着问题去看源码,这样会更有目的性,效率自然会大大提高。
从源码中想得到什么
对于 keep-alive
在没有看源码之前我有以下几个疑问
keep-alive
缓存的是什么?vnode 吗?keep-alive
是把组件缓存到哪里了?keep-alive
的工作流程是什么?
下面我们就来带着问题来看源码
首先 keep-alive
也是一个组件,我们先找到组件的实例,源码如下所示:
const KeepAliveImpl: ComponentOptions = {
name: `KeepAlive`,
__isKeepAlive: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},
setup(props: KeepAliveProps, { slots }: SetupContext) {
...
}
}
首先我们可以看到 keep-alive
接受三个props分别是
- include 待缓存组件名称
- exclude 排除缓存的组件名称
- max 允许最大缓存组件数
接下来我们具体看下 setup
函数,如下所示:
setup(props: KeepAliveProps, { slots }: SetupContext) {
// 获取 keep-alive 实例 并保证一定存在
const instance = getCurrentInstance()!
const sharedContext = instance.ctx as KeepAliveContext
if (!sharedContext.renderer) {
return slots.default
}
// map 用于缓存 组件的vnode
const cache: Cache = new Map()
// 用于缓存组件的key
const keys: Keys = new Set()
let current: VNode | null = null
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
;(instance as any).__v_cache = cache
}
...
}
从代码中我们可以看出 keep-alive
是使用 Map
来缓存组件的,后面的代码是关于钩子函数的执行和对于缓存组件的维护。
watch 后面有一段非常重要的代码就是cacheSubtree
函数,用来缓存组件
const cacheSubtree = () => {
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
}
}
我们再来看看 getInnerChild
返回的是什么,就证明了 keep-alive
缓存的是不是vnode
function getInnerChild(vnode: VNode) {
return vnode.shapeFlag & ShapeFlags.SUSPENSE ? vnode.ssContent! : vnode
}
可以看出 keep-alive
缓存的就是组件的vnode。
接下来我们再来看看 setup
的 return (渲染)函数
return () => {
// key
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)
console.log('vnode', vnode === rawVNode)
//使用vnode的type当做key
const comp = vnode.type as ConcreteComponent
const name = getComponentName(
isAsyncWrapper(vnode)
? (vnode.type as ComponentOptions).__asyncResolved || {}
: comp
)
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)
if (vnode.el) {
vnode = cloneVNode(vnode)
if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {
rawVNode.ssContent = vnode
}
}
pendingCacheKey = key
if (cachedVNode) {
vnode.el = cachedVNode.el
vnode.component = cachedVNode.component
if (vnode.transition) {
setTransitionHooks(vnode, vnode.transition!)
}
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
keys.delete(key)
keys.add(key)
} else {
keys.add(key)
if (max && keys.size > parseInt(max as string, 10)) {
pruneCacheEntry(keys.values().next().value)
}
}
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
current = vnode
return rawVNode
}
}
主要是对插槽的一些判断以及对props函数的处理,最后返回也是需要渲染的vnode
总结
从源码中可以看出 keep-alive
就是通过 Map 缓存组件的 vnode 以及会给每个组件做一个标识,这样在渲染的时候就不会再执行组件的初始化函数。