• 售前

  • 售后

热门帖子
入门百科

从源码角度来回复keep-alive组件的缓存原理

[复制链接]
普通人物怨 显示全部楼层 发表于 2021-10-25 19:18:52 |阅读模式 打印 上一主题 下一主题
本日开门见山地聊一下口试中被问到的一个问题:keep-alive组件的缓存原理。

官方API先容和用法

      
  • <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。  
  • 和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。  
  • 当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应实行。
官网的例子是 tab 切换生存了用户的操纵,现实中还可能遇到从列表页跳转去了详情页,再跳转回列表页必要生存用户举行过的筛选操纵,这就必要用到 <keep-alive>,这样也能避免重新渲染,进步页面性能。

用法及props的讲解

  1. // keep-alive组件搭配动态组件的用法,还要其他的用法参见官网
  2. <keep-alive
  3. include="['componentNameA', 'componentNameB']"
  4. exclude="'componentNameC'"
  5. :max="10">
  6. <component :is="view"></component>
  7. </keep-alive>
复制代码
      
  • include - 字符串或正则表达式或数组,name匹配上的组件会被缓存  
  • exclude - 字符串或正则表达式或数组,name匹配上的组件都不会被缓存  
  • max - 字符串或数字,缓存组件实例的最大数,最久没有被访问的实例会被销毁掉
注意:

      
  • <keep-alive> 只渲染其直系的一个组件,因此若在 <keep-alive> 中用 v-for,则其不会工作,若多条件判断有多个符合条件也同理不工作。  
  • include 和 exclude 匹配时,首先查抄组件的 name 选项,若 name 选项不可用,则匹配它的局部注册名称 (即父组件 components 选项的键值)。匿名组件不能被匹配。  
  • <keep-alive> 不会在函数式组件中正常工作,因为它们没有缓存实例。
源码解读


先贴一张源码图

统共125行,收起来一看其实东西也比较少。前面是引入一些必要用到的方法,然后界说了一些  keep-alive 组件自己会用到的一些方法,末了就是向外袒露一个 name 为 keep-alive 的组件选项,这些选项除了 abstract 外,其他的我们都比较熟悉了,此中, render 函数就是缓存原理最紧张的部分,也能看出 keep-alive 组件是一个函数式组件。
  1. // isRegExp函数判断是不是正则表达式,remove移除数组中的某一个成员
  2. // getFirstComponentChild获取VNode数组的第一个有效组件
  3. import { isRegExp, remove } from 'shared/util'
  4. import { getFirstComponentChild } from 'core/vdom/helpers/index'
  5. type VNodeCache = { [key: string]: ?VNode }; // 缓存组件VNode的缓存类型
  6. // 通过组件的name或组件tag来获取组件名(上面注意的第二点)
  7. function getComponentName (opts: ?VNodeComponentOptions): ?string {
  8. return opts && (opts.Ctor.options.name || opts.tag)
  9. }
  10. // 判断include或exclude跟组件的name是否匹配成功
  11. function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  12. if (Array.isArray(pattern)) {
  13. return pattern.indexOf(name) > -1 // include或exclude是数组的情况
  14. } else if (typeof pattern === 'string') {
  15. return pattern.split(',').indexOf(name) > -1 // include或exclude是字符串的情况
  16. } else if (isRegExp(pattern)) {
  17. return pattern.test(name) // include或exclude是正则表达式的情况
  18. }
  19. return false // 都没匹配上(上面注意的二三点)
  20. }
  21. // 销毁缓存
  22. function pruneCache (keepAliveInstance: any, filter: Function) {
  23. const { cache, keys, _vnode } = keepAliveInstance // keep-alive组件实例
  24. for (const key in cache) {
  25. const cachedNode: ?VNode = cache[key] // 已经被缓存的组件
  26. if (cachedNode) {
  27.   const name: ?string = getComponentName(cachedNode.componentOptions)
  28.   // 若name存在且不能跟include或exclude匹配上就销毁这个已经缓存的组件
  29.   if (name && !filter(name)) {
  30.   pruneCacheEntry(cache, key, keys, _vnode)
  31.   }
  32. }
  33. }
  34. }
  35. // 销毁缓存的入口
  36. function pruneCacheEntry (
  37. cache: VNodeCache,
  38. key: string,
  39. keys: Array<string>,
  40. current?: VNode
  41. ) {
  42. const cached = cache[key] // 被缓存过的组件
  43. // “已经被缓存的组件是否继续被缓存” 有变动时
  44. // 若组件被缓存命中过且当前组件不存在或缓存命中组件的tag和当前组件的tag不相等
  45. if (cached && (!current || cached.tag !== current.tag)) {
  46. // 说明现在这个组件不需要被继续缓存,销毁这个组件实例
  47. cached.componentInstance.$destroy()
  48. }
  49. cache[key] = null // 把缓存中这个组件置为null
  50. remove(keys, key) // 把这个组件的key移除出keys数组
  51. }
  52. // 示例类型
  53. const patternTypes: Array<Function> = [String, RegExp, Array]
  54. // 向外暴露keep-alive组件的一些选项
  55. export default {
  56. name: 'keep-alive', // 组件名
  57. abstract: true, // keep-alive是抽象组件
  58. // 用keep-alive组件时传入的三个props
  59. props: {
  60. include: patternTypes,
  61. exclude: patternTypes,
  62. max: [String, Number]
  63. },
  64. created () {
  65. this.cache = Object.create(null) // 存储需要缓存的组件
  66. this.keys = [] // 存储每个需要缓存的组件的key,即对应this.cache对象中的键值
  67. },
  68. // 销毁keep-alive组件的时候,对缓存中的每个组件执行销毁
  69. destroyed () {
  70. for (const key in this.cache) {
  71.   pruneCacheEntry(this.cache, key, this.keys)
  72. }
  73. },
  74. // keep-alive组件挂载时监听include和exclude的变化,条件满足时就销毁已缓存的组件
  75. mounted () {
  76. this.$watch('include', val => {
  77.   pruneCache(this, name => matches(val, name))
  78. })
  79. this.$watch('exclude', val => {
  80.   pruneCache(this, name => !matches(val, name))
  81. })
  82. },
  83. // 重点来了
  84. render () {
  85. const slot = this.$slots.default // keep-alive组件的默认插槽
  86. const vnode: VNode = getFirstComponentChild(slot) // 获取默认插槽的第一个有效组件
  87. // 如果vnode存在就取vnode的选项
  88. const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
  89. if (componentOptions) {
  90.   //获取第一个有效组件的name
  91.   const name: ?string = getComponentName(componentOptions)
  92.   const { include, exclude } = this // props传递来的include和exclude
  93.   if (
  94.   // 若include存在且name不存在或name未匹配上
  95.   (include && (!name || !matches(include, name))) ||
  96.   // 若exclude存在且name存在或name匹配上
  97.   (exclude && name && matches(exclude, name))
  98.   ) {
  99.   return vnode // 说明不用缓存,直接返回这个组件进行渲染
  100.   }
  101.   
  102.   // 匹配上就需要进行缓存操作
  103.   const { cache, keys } = this // keep-alive组件的缓存组件和缓存组件对应的key
  104.   // 获取第一个有效组件的key
  105.   const key: ?string = vnode.key == null
  106.   // 同一个构造函数可以注册为不同的本地组件
  107.   // 所以仅靠cid是不够的,进行拼接一下
  108.   ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
  109.   : vnode.key
  110.   // 如果这个组件命中缓存
  111.   if (cache[key]) {
  112.   // 这个组件的实例用缓存中的组件实例替换
  113.   vnode.componentInstance = cache[key].componentInstance
  114.   // 更新当前key在keys中的位置
  115.   remove(keys, key) // 把当前key从keys中移除
  116.   keys.push(key) // 再放到keys的末尾
  117.   } else {
  118.   // 如果没有命中缓存,就把这个组件加入缓存中
  119.   cache[key] = vnode
  120.   keys.push(key) // 把这个组件的key放到keys的末尾
  121.   // 如果缓存中的组件个数超过传入的max,销毁缓存中的LRU组件
  122.   if (this.max && keys.length > parseInt(this.max)) {
  123.    pruneCacheEntry(cache, keys[0], keys, this._vnode)
  124.   }
  125.   }
  126.   vnode.data.keepAlive = true // 设置这个组件的keepAlive属性为true
  127. }
  128. // 若第一个有效的组件存在,但其componentOptions不存在,就返回这个组件进行渲染
  129. // 或若也不存在有效的第一个组件,但keep-alive组件的默认插槽存在,就返回默认插槽的第一个组件进行渲染
  130. return vnode || (slot && slot[0])
  131. }
  132. }
复制代码
增补:


上面关于删除第一个旧缓存组件和更新缓存组件 key 的次序,其实是用到了LRU缓存镌汰计谋:
LRU全称Least Recently Used,近来最少利用的意思,是一种内存管理算法。
这种算法基于一种假设:恒久不消的数据,在未来被用到的几率也很小,因此,当数据所占内存到达肯定阈值,可以移撤除近来最少利用的。
总结


简朴总结为:

keep-alive 组件在渲染的时间,会根据传入的 include 和 exclude 来匹配 keep-alive 包裹的定名组件,未匹配上就直接返回这个定名组件举行渲染,若匹配上就举行缓存操纵:若缓存中已有这个组件,就更换其实例,并更新这个组件的 key 在 keys 中的位置;若缓存中没有这个组件,就把这个组件放入 keep-alive 组件的缓存 cache 中,并把这个组件的 key 放入 keys 中,由于在 mounted 的时间有对 include 和 exclude 举行监听,因此,后续这两个属性值发生变化时,会再次判断是否满意条件而举行组件销毁。

到此这篇关于从源码角度来回答keep-alive组件的缓存原理的文章就先容到这了,更多相干keep-alive组件缓存内容请搜刮草根技术分享以前的文章或继续浏览下面的相干文章盼望大家以后多多支持草根技术分享!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作