基本用法
<keep-alive>- <div>123</div>+ <component :is="switchOne ? 'TabOne' : 'TabTwo'" />- <component :is="switchOne ? 'TabTwo' : 'TabOne'" /></keep-alive>
- keep-alive 只会去缓存 第一级 的 第一个 组件
- 我们可以通过
include/exclude/max来对缓存进行更小颗粒的控制 - 缓存的组件在切换的时候会触发
activated/deactivated的生命周期函数
组件实现原理
export default {name: 'keep-alive',abstract: true,props: {include: patternTypes,exclude: patternTypes,max: [String, Number]},created () {// 创建缓存队列以及其对应的 key 值队列this.cache = Object.create(null)this.keys = []},destroyed () {// 销毁所有已经保存的组件实例for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)}},mounted () {// 监听 include / exclued,用于让缓存队列保持一致性this.$watch('include', val => {pruneCache(this, name => matches(val, name))})this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})},render () {// 从 $slot 中获取 keep-alive 的 childrenconst slot = this.$slots.default// 找到第一个组件的最新的 VNodeconst vnode: VNode = getFirstComponentChild(slot)// 获取其配置信息const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {// 被捕获的组件名称const name: ?string = getComponentName(componentOptions)// 如果不在缓存的目标范围内,则直接返回新的 VNodeconst { include, exclude } = thisif (// not included(include && (!name || !matches(include, name))) ||// excluded(exclude && name && matches(exclude, name))) {return vnode}const { cache, keys } = this// 获得组件对应的 key 值const key: ?string = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key// LRU 缓存操作,如果缓存中存在对应的实例,则将实例注入到 VNode 中if (cache[key]) {vnode.componentInstance = cache[key].componentInstance// make current key freshestremove(keys, key)keys.push(key)} else {cache[key] = vnodekeys.push(key)// prune oldest entryif (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)}}// 标注 keepAlive,方便 Vue 配合vnode.data.keepAlive = true}// 返回return vnode || (slot && slot[0])}}
缓存核心操作的内容其实是 componentInstance,如果已经初始化过对应的实例则不用初始化,直接取出即可。
Vue 内部是如何配合的
先明确一下正常组件实例化的过程:
- AST(编译 template)
- CodeGen(生成 render 函数)
- VNode(通过 render 获得组件 VNode,并将组件的构造函数存到 VNode 中)
- Patch
- 新增节点(通过 VNode 中的构造函数初始化组件)
- 更新节点(基于旧的组件实例更新其状态)
- 删除节点
如果没有 keep-alive,组件在进行切换到时候,Vue 在 patch 会认为是新增的节点而重新创建组件实例,从而导致先前可能存在状态会被清空。
如果有 keep-alive,切换节点操作最后 patch 还是会走到 新增节点 **的逻辑,但是不一样的是,新的 VNode 中已经有组件实例,同时 VNode.data.keepAlive 标识了是一个 keep-alive 下的组件。
下面是新增组件的函数
- 这里主要是调用 VNode 的 init()
同时实例化完后会决定是否调用重新激活(activated)生命周期函数
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {let i = vnode.dataif (isDef(i)) {// 标记是否需要重新激活const isReactivated = isDef(vnode.componentInstance) && i.keepAlive// 调用 init 钩子函数if (isDef(i = i.hook) && isDef(i = i.init)) {i(vnode, false /* hydrating */)}// 如果组件实例构建成功if (isDef(vnode.componentInstance)) {initComponent(vnode, insertedVnodeQueue)insert(parentElm, vnode.elm, refElm)// 如果是重新激活的组件则调用对应的钩子函数if (isTrue(isReactivated)) {reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)}return true}}}
这里是 keep-alive 与普通组件初始化开始走向分岔路的起点
如果是 keep-alive 下的组件,同时初始化过了,则直接 patch
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {// 如果已经有组件实例 并且 没被销毁 并且有 keepAlive 则走 patch// 反之走初始化if (vnode.componentInstance &&!vnode.componentInstance._isDestroyed &&vnode.data.keepAlive) {// kept-alive components, treat as a patchconst mountedNode: any = vnode // work around flowcomponentVNodeHooks.prepatch(mountedNode, mountedNode)} else {const child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance)child.$mount(hydrating ? vnode.elm : undefined, hydrating)}},
