所以在执行createElm(keepAliveVnode)的过程中会对keep-alive组件的实例化及挂载,而在实例化的过程中,keep-alive包裹的子组件的vnode会赋值给keep-alive组件实例的$slot属性,所以在keep-alive实例调用render函数时,可以通过this.$slot拿到包裹组件的vnode,在demo中,就是Foo组件的vnode,具体分析下keep-alive组件的render函数
render () { const slot = this.$slots.default const vnode: VNode = getFirstComponentChild(slot) const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { const name: ?string = getComponentName(componentOptions) const { include, exclude } = this if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } const { cache, keys } = this const key: ?string = 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]) }
上面分析到,在执行createElm(keepAliveVnode)的过程中,会执行keep-alive组件的实例化及挂载($mount),而在挂载的过程中,会执行keep-alive的render函数,之前分析过,在render函数中,可以通过this.$slot获取到子组件的vnode,从上面源码中,可以知道,keep-alive只处理默认插槽的第一个子组件,言外之意如果在keep-alive中包裹多个组件的话,剩下的组件会被忽略,例如:
<keep-alive> <Foo /> <Bar /> </keep-alive> // 只会渲染Foo组件
继续分析,在拿到Foo组件vnode后,判断了componentOptions,由于我们的Foo是一个组件,所以这里componentOptions是存在的,进到if逻辑中,此处include 表示只有匹配的组件会被缓存,而 exclude 表示任何匹配的组件都不会被缓存,demo中并没有设置相关规则,此处先忽略。
const { cache, keys } = this cache, keys是在keep-alive组件的create钩子中生成的,用来存储被keep-alive缓存的组件的实例以及对应vnode的key created () { this.cache = Object.create(null) this.keys = [] }
继续下面
const key: ?string = 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的key,如果vnode.key存在则使用vnode.key,不存在则用componentOptions.Ctor.cid + (componentOptions.tag ?::${componentOptions.tag}: '')作为存储组件实例的key,据此可以知道,如果我们不指定组件的key的话,对于相同的组件会匹配到同一个缓存,这也是为什么最开始在描述keep-alive的时候强调它是一个组件级的缓存方案。
那么首次渲染的时候,cache和keys都是空的,这里就会走else逻辑
cache[key] = vnode keys.push(key) if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) }
以key作为cache的健进行存储Foo组件vnode(注意此时vnode上面还没有componentInstance),这里利用了对象存储的原理,之后进行Foo组件实例化的时候会将其实例赋值给vnode.componentInstance,那么在下次keep-alive组件render的时候就可以获取到vnode.componentInstance。
所以首次渲染仅仅是在keep-alive的cache上面,存储了包裹组件Foo的vnode。
针对包裹组件的渲染
上面已经讲到执行了keep-alive的render函数,根据上面的源码可以知道,render函数返回了Foo组件的vnode,那么在keep-alive执行patch的时候,会创建Foo组件的实例,然后再进行Foo组件的挂载,这个过程与普通组件并没有区别,在此不累述。
当组件从Foo切换到Bar时
本例中由于renderCom属性的变化,会触发根组件的renderWatcher,之后会执行patch(oldVnode, vnode)
在进行child vnode比较的时候,keep-alive的新老vnode比较会被判定为sameVnode,之后会进入到patchVnode的逻辑