这是我参与更文挑战的第4天,活动详情查看: 更文挑战vue
注:如下是我的理解、若有不对还望指正!node
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出如今组件的父组件链中当组件在 内被切换,组件在活跃和不活跃触发activated 和 deactivated 这两个生命周期钩子函数缓存
<div id="app">
<keep-alive include="test"> <test></test> <demo></demo> </keep-alive>
</div>
const test = {
name:'test',
template:"<div> {{ componentsName }} </div>",
data(){
return {
componentName:'test测试组件'
}
},
activated(){
console.log('test-activated')
}
}
const demo = {
name:'demo',
template:"<div> {{ name }} </div>",
data(){
return {
componentName:'demo测试组件'
}
},
activated(){
console.log('demo_keep-alive')
}
}
const vm = new Vue({
components:{
test,
demo
},
el:'#app'
})
复制代码
咱们发现页面只会对第一个test组件进行展现、奇怪了为何我只有test呢、那demo组件去哪里了呢?带着这些疑问咱们打开vue的源码看下知其因此然....markdown
我会把主要源码贴入进来、包括代码的注释、组件文件地址:
/src/core/components/keep-alive.js
app
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
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)
}
/* istanbul ignore next */
return false
}
复制代码
function pruneCache (keepAliveInstance: any, filter: Function) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode: ?VNode = cache[key]
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
复制代码
created () {
this.cache = Object.create(null)
this.keys = []
}
复制代码
mounted () {
//监听配置、重置缓存信息
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
复制代码
//执行页面render
render () {
const slot = this.$slots.default
//获取kepp-alive组件下的第一个子组件vndoe
const vnode: VNode = getFirstComponentChild(slot)
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// 获取组件名称
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this;
//判断是不是须要缓存、不须要直接走这if
if (
// 有include和没有获取到name值 或者 include是否包含name值
(include && (!name || !matches(include, name))) ||
// 是不是白名单、直接过滤
(exclude && name && matches(exclude, name))
) {
return vnode
}
//须要缓存逻辑
const { cache, keys } = this
//判断是否有key、若是没有vue会自动给他加上key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
//当前是否已经有缓存下来的组件数据、有直接取缓存的
if (cache[key]) {
//赋值缓存的vnode
vnode.componentInstance = cache[key].componentInstance
// 从新调整组件key的位置、目的是为了若是数据最近被访问过,那么未来被访问的概率也更高
remove(keys, key)
keys.push(key)
} else {
//保存缓存vnode数据
cache[key] = vnode
//添加key
keys.push(key)
// 判断是否超过最大缓存值
if (this.max && keys.length > parseInt(this.max)) {
//超过就删除第一个保存的vnode
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
//添加keepAlive = true标记
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
复制代码
大体的缓存流程,vue有个内置组件、这个组件维护着缓存对象和被缓存组件的key名称、提升你传入的include, exclude判断是否须要缓存当前组件、并且咱们在render函数发现组件永远都只会取第一个子组件内容、而咱们案例上的demo组件永远没有机会显示出来、其实也有办法那就是给他们包裹vue提供的另一个内置组件component、判断显示那个组件、而后keep-alive会提升当前组件是否设置了白名单或者不是include配置项组件那就直接return vnode,遇到缓存的vnode、先判断缓存对象是否已经存若是存在直接取缓存vnode (有个小细节、从新调整组件key的位置、目的是为了若是数据最近被访问过,那么未来被访问的概率也更高、由于可能缓存到必定max数量、会遇到删除栈的vnode、这个时候是根据key的位置在操做的) 、不存在的话往缓存对象添加记录函数