keepalive缓存组件的缓存原理是什么?
Admin 2021-05-10 群英技术资讯 510 次浏览
这篇文章主要给大家介绍的是keep-alive组件的缓存原理,很多对于keepalive缓存组件的用法可能比较熟练,但是其缓存原理并不是很清楚,对此下面会从源码角度与大家一起探讨一下keep-alive组件的缓存原理。
官网的例子是 tab 切换保存了用户的操作,实际中还可能遇到从列表页跳转去了详情页,再跳转回列表页需要保存用户进行过的筛选操作,这就需要用到 <keep-alive>,这样也能避免重新渲染,提高页面性能。
// keep-alive组件搭配动态组件的用法,还要其他的用法参见官网 <keep-alive include="['componentNameA', 'componentNameB']" exclude="'componentNameC'" :max="10"> <component :is="view"></component> </keep-alive>
注意:
先贴一张源码图
总共125行,收起来一看其实东西也比较少。前面是引入一些需要用到的方法,然后定义了一些 keep-alive 组件自己会用到的一些方法,最后就是向外暴露一个 name 为 keep-alive 的组件选项,这些选项除了 abstract 外,其他的我们都比较熟悉了,其中, render 函数就是缓存原理最重要的部分,也能看出 keep-alive 组件是一个函数式组件。
// isRegExp函数判断是不是正则表达式,remove移除数组中的某一个成员 // getFirstComponentChild获取VNode数组的第一个有效组件 import { isRegExp, remove } from 'shared/util' import { getFirstComponentChild } from 'core/vdom/helpers/index' type VNodeCache = { [key: string]: ?VNode }; // 缓存组件VNode的缓存类型 // 通过组件的name或组件tag来获取组件名(上面注意的第二点) function getComponentName (opts: ?VNodeComponentOptions): ?string { return opts && (opts.Ctor.options.name || opts.tag) } // 判断include或exclude跟组件的name是否匹配成功 function matches (pattern: string | RegExp | Array<string>, name: string): boolean { if (Array.isArray(pattern)) { return pattern.indexOf(name) > -1 // include或exclude是数组的情况 } else if (typeof pattern === 'string') { return pattern.split(',').indexOf(name) > -1 // include或exclude是字符串的情况 } else if (isRegExp(pattern)) { return pattern.test(name) // include或exclude是正则表达式的情况 } return false // 都没匹配上(上面注意的二三点) } // 销毁缓存 function pruneCache (keepAliveInstance: any, filter: Function) { const { cache, keys, _vnode } = keepAliveInstance // keep-alive组件实例 for (const key in cache) { const cachedNode: ?VNode = cache[key] // 已经被缓存的组件 if (cachedNode) { const name: ?string = getComponentName(cachedNode.componentOptions) // 若name存在且不能跟include或exclude匹配上就销毁这个已经缓存的组件 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和当前组件的tag不相等 if (cached && (!current || cached.tag !== current.tag)) { // 说明现在这个组件不需要被继续缓存,销毁这个组件实例 cached.componentInstance.$destroy() } cache[key] = null // 把缓存中这个组件置为null remove(keys, key) // 把这个组件的key移除出keys数组 } // 示例类型 const patternTypes: Array<Function> = [String, RegExp, Array] // 向外暴露keep-alive组件的一些选项 export default { name: 'keep-alive', // 组件名 abstract: true, // keep-alive是抽象组件 // 用keep-alive组件时传入的三个props props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created () { this.cache = Object.create(null) // 存储需要缓存的组件 this.keys = [] // 存储每个需要缓存的组件的key,即对应this.cache对象中的键值 }, // 销毁keep-alive组件的时候,对缓存中的每个组件执行销毁 destroyed () { for (const key in this.cache) { pruneCacheEntry(this.cache, key, this.keys) } }, // keep-alive组件挂载时监听include和exclude的变化,条件满足时就销毁已缓存的组件 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 // keep-alive组件的默认插槽 const vnode: VNode = getFirstComponentChild(slot) // 获取默认插槽的第一个有效组件 // 如果vnode存在就取vnode的选项 const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { //获取第一个有效组件的name const name: ?string = getComponentName(componentOptions) const { include, exclude } = this // props传递来的include和exclude if ( // 若include存在且name不存在或name未匹配上 (include && (!name || !matches(include, name))) || // 若exclude存在且name存在或name匹配上 (exclude && name && matches(exclude, name)) ) { return vnode // 说明不用缓存,直接返回这个组件进行渲染 } // 匹配上就需要进行缓存操作 const { cache, keys } = this // keep-alive组件的缓存组件和缓存组件对应的key // 获取第一个有效组件的key const key: ?string = vnode.key == null // 同一个构造函数可以注册为不同的本地组件 // 所以仅靠cid是不够的,进行拼接一下 ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key // 如果这个组件命中缓存 if (cache[key]) { // 这个组件的实例用缓存中的组件实例替换 vnode.componentInstance = cache[key].componentInstance // 更新当前key在keys中的位置 remove(keys, key) // 把当前key从keys中移除 keys.push(key) // 再放到keys的末尾 } else { // 如果没有命中缓存,就把这个组件加入缓存中 cache[key] = vnode keys.push(key) // 把这个组件的key放到keys的末尾 // 如果缓存中的组件个数超过传入的max,销毁缓存中的LRU组件 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } vnode.data.keepAlive = true // 设置这个组件的keepAlive属性为true } // 若第一个有效的组件存在,但其componentOptions不存在,就返回这个组件进行渲染 // 或若也不存在有效的第一个组件,但keep-alive组件的默认插槽存在,就返回默认插槽的第一个组件进行渲染 return vnode || (slot && slot[0]) } }
上面关于删除第一个旧缓存组件和更新缓存组件 key 的顺序,其实是用到了LRU缓存淘汰策略:LRU全称Least Recently Used,最近最少使用的意思,是一种内存管理算法。这种算法基于一种假设:长期不用的数据,在未来被用到的几率也很小,因此,当数据所占内存达到一定阈值,可以移除掉最近最少使用的。
简单总结为:keep-alive 组件在渲染的时候,会根据传入的 include 和 exclude 来匹配 keep-alive 包裹的命名组件,未匹配上就直接返回这个命名组件进行渲染,若匹配上就进行缓存操作:若缓存中已有这个组件,就替换其实例,并更新这个组件的 key 在 keys 中的位置;若缓存中没有这个组件,就把这个组件放入 keep-alive 组件的缓存 cache 中,并把这个组件的 key 放入 keys 中,由于在 mounted 的时候有对 include 和 exclude 进行监听,因此,后续这两个属性值发生变化时,会再次判断是否满足条件而进行组件销毁。
现在大家对于keepalive缓存组件的缓存原理应该都有有所了解了,上述代码有一定的参考价值,有需要的朋友可以了解一下,希望文本对大家学习和理解keepalive缓存组件有帮助。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
在本篇文章里小编给大家整理的是一篇关于js用正则表达式筛选年月日的实例方法,对此有兴趣的朋友们可以学习下。
使用组件库时,图标往往不能满足需求,所以我们常常需要用到第三方图标库,这篇文章主要给大家介绍了关于vue引入iconfont的相关资料,需要的朋友可以参考下
这篇文章主要为大家详细介绍了JavaScript实现简易左右点击切换图片,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
Vue生命周期是指vue实例对象从创建之初到销毁的过程,vue所有功能的实现都是围绕其生命周期进行的,下面这篇文章主要给大家介绍了关于Vue生命周期和钩子函数的相关资料,需要的朋友可以参考下
这篇文章主要为大家详细介绍了jQuery实现表单验证功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008