Vue 中有一个特别好用的组件 keep-alive 组件,咱们在不少场景下,都是能够借助于这个组件来提高咱们的产品体验,基本上0成本实现缓存效果。用的最多的场景就是和路由搭配,缓存不怎么更新的路由页面。html
那这么好用的功能,背后是怎么实现的,有哪些能够学习的,一块儿来分析下。前端
来自官网的介绍 cn.vuejs.org/v2/api/#kee…vue
能够看到,他的使用,更多的是跟随者动态组件一块儿使用。最核心的就是缓存组件实例,以提高性能。在官网上也有一个tab切换的示例,就是他的一种使用场景 cn.vuejs.org/v2/guide/co…node
既然是一个内置组件,那么它确定也就是一个按照组件来定义的,Vue 中核心的实如今 github.com/vuejs/vue/b… 这里git
export default {
// 组件名字
name: 'keep-alive',
// 抽象组件 这是一个没有对外暴露的组件声明属性
// 做用的话,就是不渲染DOM 也不会出如今父组件的children中
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
methods: {
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
// 存在须要缓存的节点
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
// 缓存到 cache 对象中
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag,
componentInstance,
}
keys.push(keyToCache)
// 判断是否超出了 max 最大缓存实例数
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
// 若是超出了 就销毁超出的
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
},
created () {
// 初始化缓存对象
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
// 销毁的时候 全部实例所有销毁
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.cacheVNode()
// 若是这些有更新 同样须要再次 check 一遍全部的缓存实例 是否应该缓存
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
updated () {
// 更新钩子 再次缓存
this.cacheVNode()
},
render () {
// 重点 render 的实现
// 能够得到组件内的默认内容 其实也就是 默认插槽内容
const slot = this.$slots.default
// 找到里边第一个组件节点
const vnode: VNode = getFirstComponentChild(slot)
// 经过 vnode 节点能够得到组件配置项
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
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
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// 以前缓存过了
// 直接使用以前缓存的组件实例
vnode.componentInstance = cache[key].componentInstance
// 先删除掉这个 key 而后在push 保证这个 key 是新鲜的 超限check的时候 有用
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
// delay setting the cache until update
// 设置好 vnodeToCache 当更新的时候 再去缓存 参考 updated 钩子中的逻辑
this.vnodeToCache = vnode
this.keyToCache = key
}
vnode.data.keepAlive = true
}
// 返回的就是内部的节点
return vnode || (slot && slot[0])
}
}
复制代码
上边大概就是核心的一个流程:github
你会发现要想很好的理解上述过程,要很好的理解 Vue 的生命周期,能够参考 cn.vuejs.org/v2/guide/in… 图以下:api
此外,还有不少关于 vnode 上的属性,如:componentOptions、key、data、componentInstance、tag 等,以及相配合的在 Vue 中是如何识别和运用这些属性的:如何不创新新的实例,如何触发新的生命周期钩子 activated deactivated 等,若是你对全部的逻辑细节比较感兴趣,能够参考黄老师的 ustbhuangyi.github.io/vue-analysi…数组
咱们能够理解为 Vue 为何提供了内置组件 keep-alive?缓存
在前言的部分,咱们也讲了在实际场景中,仍是会遇到很多缓存组件的状况,在遇到路由场景的时候更甚。markdown
那 Vue 的一个理念就是对开发者很友好,框架作了不少事情,使得开发者能够专一于自身的逻辑开发工做,这也是为何全球会有那么多开发者钟爱它的缘由之一。那从这个点出发,由于有这么多的需求,因此 Vue 也就提供了这么好用的内置组件也就不难理解了。
咱们能够看出,keep-alive 的组件实现并不复杂,所有文件也就 150 行上下,可是功能却很强大,全部的功能参考 cn.vuejs.org/v2/api/#kee… 。那么从这个组件上,咱们能够学到些什么东西,有什么能够借鉴的吗?
组件定义虽然很少,可是倒是用到了 Vue 中绝大多数的生命周期钩子,且是咱们也能常用到的:created、mounted、updated、destroyed。这也是咱们写好组件的基石,正确理解并运用他们,知道他们的关系和过程,以及在对应的生命周期中适合作什么样的事情。
额外提一点,在 created 生命周期钩子函数中,咱们看到了如何给实例定义一些非响应式的对象,能够在 created 中直接 this.xxx = xxxx
的这种方式,而不是习惯性的把这些属性放到 data()
中去定义(会变为响应式对象,增长额外的开销),这种技巧值得咱们学习和应用。
在这里虽然十分简单,直接返回了默认插槽内容的节点,可是咱们能够从这个点出发,在一些特殊场景,仍是须要咱们去手写 render 的,这部分也是须要咱们去熟练运用的,详细的能够参考官网 cn.vuejs.org/v2/guide/re… 关于 render 函数的使用以及createElement、数据对象。
keys 是一个数组,咱们知道 keep-alive 还提供了一个能力:max
这个 prop,指定了最多能够缓存多少组件实例,一旦这个数字达到了,在新实例被建立以前,已缓存组件中最久没有被访问的实例会被销毁掉。注意这里的关键:最久没有被访问的实例会被销毁掉。
这个是怎么作到的,排序吗?不用那么麻烦,经过上边的分析咱们知道Vue中采用了一个很巧妙的作法:
remove(keys, key)
keys.push(key)
复制代码
简单来讲,将 keys 中如今对应的 key 删除掉,而后把这个 key 再 push 到数组最后便可。
经过这种方式,就能够保障了 keys 这个数组中最尾部的元素就是最新鲜的元素,最开始的元素就是最不新鲜的,在咱们的场景中就能够对应为:最近被访问的实例。
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
复制代码
能够看到实际代码也是这样的,当超出 max 的时候,就是销毁的keys[0]
对应的组件实例。
在 destroyed 中,销毁了全部的实例,以释放在 cache 对象中缓存的对象们,这个就是对内存的管理,防止内存泄漏问题。这个是明面的内存销毁逻辑,你们只要注意了就不会遇到内存泄漏的问题了。
可是这个不是绝对的,咱们看一下最新的这个PR github.com/vuejs/vue/p… ,咱们发现解决了两个内存泄漏的 issue,直观看起来是不该该存在内存泄漏的才对。
这里就惊醒咱们,要注意内存泄漏问题,他可能会是因为咱们不经意之间写的代码所致使的。咱们须要作到怎么利用开发者工具:
$destroy()
滴滴前端技术团队的团队号已经上线,咱们也同步了必定的招聘信息,咱们也会持续增长更多职位,有兴趣的同窗能够一块儿聊聊。