keep-alive源码分析
[[toc]]
如何使用
想看具体用法看我上一篇文章 这大概是最全乎的keep-alive的踩坑指南
源码剖析
源码地址
 import {isRegExp, remove} from 'shared/util' import {getFirstComponentChild} from 'core/vdom/helpers/index'
 
  function getComponentName(opts) {   return opts && (opts.Ctor.options.name || opts.tag) }
  function matches(pattern, name) {   if (Array.isArray(pattern)) {     return pattern.indexOf(name) > -1   } else if (typeof pattern === 'string') {     return pattern.split(',').indexOf(name) > -1   } else if (isRegExp(pattern)) {     return pattern.test(name)   }   return false }
 
  function pruneCache(keepAliveInstance, filter) {   const {cache, keys, _vnode} = keepAliveInstance   for (const key in cache) {     const cachedNode = cache[key]     if (cachedNode) {       const name = getComponentName(cachedNode.componentOptions)       if (name && !filter(name)) {                  pruneCacheEntry(cache, key, keys, _vnode)       }     }   } }
  function pruneCacheEntry(cache, key, keys, current) {   const cached = cache[key]   if (cached && (!current || cached.tag !== current.tag)) {     cached.componentInstance.$destroy()    }   cache[key] = null   remove(keys, key) }
  const patternTypes = [String, RegExp, Array]
  export default {   name: 'keep-alive',      abstract: true,   props: {     include: patternTypes,      exclude: patternTypes,      max: [String, Number]    },   created() {     this.cache = Object.create(null)       this.keys = []    },   destroyed() {     for (const key in this.cache) {              pruneCacheEntry(this.cache, key, this.keys)     }   },   mounted() {      
 
 
 
 
 
 
 
 
           this.$watch('include', val => {       pruneCache(this, name => matches(val, name))     })     this.$watch('exclude', val => {       pruneCache(this, name => !matches(val, name))     })   },   render() {          const slot = this.$slots.default          const vnode = getFirstComponentChild(slot)     const componentOptions = vnode && vnode.componentOptions          if (componentOptions) {       const name = getComponentName(componentOptions)       const {include, exclude} = this              if ((include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name))) {         return vnode       }
        const {cache, keys} = this              const key = vnode.key == null ?         componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') :         vnode.key
        if (cache[key]) {                  vnode.componentInstance = cache[key].componentInstance         remove(keys, key)           keys.push(key)         } else {                  cache[key] = vnode         keys.push(key)         if (this.max && keys.length > parseInt(this.max)) {                                 pruneCacheEntry(cache, keys[0], keys, this._vnode)         }       }
        vnode.data.keepAlive = true       }     return vnode || (slot && slot[0])   } }
 
  | 
 
其实大致可以分为这几步:
- 在要缓存的组件上使用keep-alive标签
 
- 根据传递的参数,看是否要添加缓存和限制的个数,不缓存直接返回你当前的vnode,若需要缓存就根据生成的key进行对象存储
 
- 存储的过程要注意 max 和存储的位置,如果大于max就要把索引是1的key删除, 实现置换位置。
 
- 将该组件实例的keepAlive属性值设置为true(this.$vnode.data.keepAlive 可以获取到,多的两个声明周期都是通过这个判断) 
 
钩子函数
只执行一次的钩子
keep-alive是使用你之前存储的vnode,然后直接转换成真实dom,是发生在diff之后 patch阶段,所以缓存的组件是没有 created,mounted 这些生命周期的,具体看下面的代码分析。
 const componentVNodeHooks = {   init (vnode: VNodeWithData, hydrating: boolean): ?boolean {     if (       vnode.componentInstance &&       !vnode.componentInstance._isDestroyed &&       vnode.data.keepAlive       ) {       const mountedNode: any = vnode       componentVNodeHooks.prepatch(mountedNode, mountedNode)      } else {       const child = vnode.componentInstance = createComponentInstanceForVnode(         vnode,         activeInstance       )       child.$mount(hydrating ? vnode.elm : undefined, hydrating)     }   },
    prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {     const options = vnode.componentOptions     const child = vnode.componentInstance = oldVnode.componentInstance     updateChildComponent(       child,        options.propsData,        options.listeners,        vnode,        options.children      )   },   insert (vnode) {    const { context, componentInstance } = vnode    if (!componentInstance._isMounted) {      componentInstance._isMounted = true      callHook(componentInstance, 'mounted')    }    if (vnode.data.keepAlive) {      if (context._isMounted) {        queueActivatedComponent(componentInstance)      } else {        activateChildComponent(componentInstance, true)       }    }  }, destroy (vnode) {  const { componentInstance } = vnode  if (!componentInstance._isDestroyed) {    if (!vnode.data.keepAlive) {      componentInstance.$destroy()    } else {      deactivateChildComponent(componentInstance, true)     } 	 } 	}   }
 
  | 
 
可以看出,当vnode.componentInstance(第一次进来是空的)和keepAlive同时为true时,不再进入$mount过程,那mounted之前的所有钩子函数(beforeCreate、created、mounted)都不再执行。
调用activated
在patch的阶段,最后会执行invokeInsertHook函数,而这个函数就是去调用组件实例(VNode)自身的insert钩子,就是上面的那段代码。
 function invokeInsertHook (vnode, queue, initial) {         if (isTrue(initial) && isDef(vnode.parent)) {     vnode.parent.data.pendingInsert = queue   } else {     for (let i = 0; i < queue.length; ++i) {       queue[i].data.hook.insert(queue[i])      }   } }
 
  | 
 
就是上面componentVNodeHooks的insert的方法
 insert (vnode) {   const { context, componentInstance } = vnode   if (!componentInstance._isMounted) {     componentInstance._isMounted = true     callHook(componentInstance, 'mounted')   }   if (vnode.data.keepAlive) {     if (context._isMounted) {       queueActivatedComponent(componentInstance)     } else {       activateChildComponent(componentInstance, true)      }   } }
  | 
 
看下activateChildComponent函数
 export function deactivateChildComponent (vm, direct) {   if (!vm._inactive) {     vm._inactive = true     for (let i = 0; i < vm.$children.length; i++) {       deactivateChildComponent(vm.$children[i])     }     callHook(vm, 'deactivated')    } }
 
  | 
 
deactivated钩子函数也是一样的原理,在组件实例(VNode)的destroy钩子函数中调用deactivateChildComponent函数。
渲染
keep-alive组件的渲染
/src/core/instance/lifecycle.js
 export function initLifecycle (vm: Component) {   const options = vm.$options       let parent = options.parent   if (parent && !options.abstract) {     while (parent.$options.abstract && parent.$parent) {       parent = parent.$parent     }     parent.$children.push(vm)   }
 
 
 
  | 
 
keep-alive包裹的组件是如何使用缓存的?
在patch阶段,会执行createComponent函数:
/src/core/vdom/patch.js
 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {   let i = vnode.data   if (isDef(i)) {          const isReactivated = isDef(vnode.componentInstance) && i.keepAlive     if (isDef(i = i.hook) && isDef(i = i.init)) {       i(vnode, false)     }     if (isDef(vnode.componentInstance)) {       initComponent(vnode, insertedVnodeQueue)       insert(parentElm, vnode.elm, refElm)        if (isTrue(isReactivated)) {          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)        }       return true     }   } }
 
  |