文章很短 只是记录下自己的感叹,大佬自行回避0 0
这两天遇到自己项目后台管理的tab页需求,写法参考过vue-element-admin的写法,但原作者也说了一些问题
于是想着自己研究下keep-alive的源码看看这究竟是怎么实现的.
/* @flow */
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'
type VNodeCache = { [key: string]: ?VNode };
//获取组件名称
//返回组件options.name 如无,则返回tag
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}
// 匹配规则
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)
}
}
}
}
// ^入口
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
//如果有缓存,不是当前的且缓存tag和当前不一致,则销毁
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
//从key中移除
remove(keys, key)
}
const patternTypes: Array<Function> = [String, RegExp, Array]
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)
}
},
//通过name监听包含和排除
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
//获取slot的全部节点
const slot = this.$slots.default
// 获取组件选项
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
//获取第一个节点 后来才想到这里是展示的文档
const vnode: VNode = getFirstComponentChild(slot)
if (componentOptions) {
// check pattern
// 判断组件选项是否包含name,如果是,则返回获取到的组件子节点
const name: ?string = getComponentName(componentOptions)
//选项里的include和exclude在这边用name起到作用
const { include, exclude } = this
if (
// 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
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
//如果不存在key,则key为option
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
//如果缓存中有这个节点
if (cache[key]) {
//移除节点再加入节点到最后
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
//直接插入节点
cache[key] = vnode
keys.push(key)
// prune oldest entry
//如果超过缓存数量,则调用删减节点
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 节点的data的keepAlive设置为真
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
后续
不能说多深入的读懂会写这个组件,但是里面用到的一些参数是如何起作用是实实在在的了解过了,于是稍微按照原文给出的方案做了封装和修改.实现了多开tab的需求.
后续后续
过了一天正好在新公司刚开发的页面提测后被测试测出bug,说是tab切换后内容没有保存,搜了下keep-alive找到代码,看到include,没动脑子都想到肯定是name出了问题,参考别的页面代码,发现公司的路由有过处理name,不到3分钟解决问题.
虽然可能熟悉keepalive后也能准确定位,但是这一次是确确实实体会到读码的好处,之前读各种参考各种自己仿照但很难有成就感,这次在这个相比vue其他代码相对简单的keep-alive中却是实实在在感受到了魅力
排版和叙述自己写着都感觉有问题,自己看看就好,勿喷,处理的代码过两天贴上