vue keep-alive的實現原理和緩存策略


使用

<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>

<keep-alive> 包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在父組件鏈中。

當組件在 <keep-alive> 內被切換,它的 activateddeactivated 這兩個生命周期鈎子函數將會被對應執行。

原理

/* keep-alive組件 */
export default {
  name: 'keep-alive',
  /* 抽象組件 */
  abstract: true,

  props: {
    include: patternTypes,
    exclude: patternTypes
  },

  created () {
    /* 緩存對象 */
    this.cache = Object.create(null)
  },

  /* destroyed鈎子中銷毀所有cache中的組件實例 */
  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache[key])
    }
  },

  watch: {
    /* 監視include以及exclude,在被修改的時候對cache進行修正 */
    include (val: string | RegExp) {
      pruneCache(this.cache, this._vnode, name => matches(val, name))
    },
    exclude (val: string | RegExp) {
      pruneCache(this.cache, this._vnode, name => !matches(val, name))
    }
  },

  render () {
    /* 得到slot插槽中的第一個組件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)

    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      /* 獲取組件名稱,優先獲取組件的name字段,否則是組件的tag */
      const name: ?string = getComponentName(componentOptions)
      /* name不在inlcude中或者在exlude中則直接返回vnode(沒有取緩存) */
      if (name && (
        (this.include && !matches(this.include, name)) ||
        (this.exclude && matches(this.exclude, name))
      )) {
        return vnode
      }
      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
      /* 如果已經做過緩存了則直接從緩存中獲取組件實例給vnode,還未緩存過則進行緩存 */
      if (this.cache[key]) {
        vnode.componentInstance = this.cache[key].componentInstance
        // 2.5.0 新增這段邏輯,使用 LRU 策略 make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      // 不命中緩存,把 vnode 設置進緩存
      else {
        this.cache[key] = vnode;
        // 2.5.0 新增這段邏輯,LRU 策略的移除。
        keys.push(key);
        // 如果配置了 max 並且緩存的長度超過了 this.max,還要從緩存中刪除第一個
        if (this.max && keys.length > parseInt(this.max)) {
            pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
        
      }
      /* keepAlive標記位 */
      vnode.data.keepAlive = true
    }
    return vnode
  }
}

  1. 獲取 keep-alive 包裹着的第一個子組件對象及其組件名
    根據設定的 include/exclude(如果有)進行條件匹配,決定是否緩存。不匹配,直接返回組件實例
  2. 根據組件 ID 和 tag 生成緩存 Key,並在緩存對象中查找是否已緩存過該組件實例。如果存在,直接取出緩存值並更新該 key 在 this.keys 中的位置(更新 key 的位置是實現 LRU 置換策略的關鍵)
  3. 在 this.cache 對象中存儲該組件實例並保存 key 值,之后檢查緩存的實例數量是否超過 max 的設置值,超過則根據 LRU 置換策略刪除最近最久未使用的實例(即是下標為 0 的那個 key)
  4. 最后組件實例的 keepAlive 屬性設置為 true,這個在渲染和執行被包裹組件的鈎子函數會用到,

LRU 策略

最近最久未使用算法。

每次從內存中找出最久的未使用數據用於置換新的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM