Vue中keep-alive組件的理解


對keep-alive組件的理解

當在組件之間切換的時候,有時會想保持這些組件的狀態,以避免反復重渲染導致的性能等問題,使用<keep-alive>包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。

描述

重新創建動態組件的行為通常是非常有用的,但是在有些情況下我們更希望那些標簽的組件實例能夠被在它們第一次被創建的時候緩存下來,此時使用<keep-alive>包裹組件即可緩存當前組件實例,將組件緩存到內存,用於保留組件狀態或避免重新渲染,和<transition>相似它,其自身不會渲染一個DOM元素,也不會出現在組件的父組件鏈中。

<keep-alive>
    <component v-bind:is="currentComponent" class="tab"></component>
</keep-alive>

<keep-alive>包含的組件不會被再次初始化,也就意味着不會重走生命周期函數,<keep-alive>保持了當前的組件的狀態,在第一次創建的時候回正常觸發其創建生命周期,但是由於組件其實並未銷毀,所以不會觸發組件的銷毀生命周期,而當組件在<keep-alive>內被切換時,它的activateddeactivated這兩個生命周期鈎子函數將會被對應執行。

export default {
    data: function() {
        return {
        
        }
    },
    activated: function(){
        console.log("activated");
    },
    deactivated: function(){
        console.log("deactivated");
    },
}

<keep-alive>可以接收3個屬性做為參數進行匹配對應的組件進行緩存,匹配首先檢查組件自身的name選項,如果name選項不可用,則匹配它的局部注冊名稱,即父組件components選項的鍵值,匿名組件不能被匹配,除了使用<keep-alive>props控制組件緩存,通常還可以配合vue-router在定義時的meta屬性以及在template定義的<keep-alive>進行組件的有條件的緩存控制。

  • include: 包含的組件,可以為字符串,數組,以及正則表達式,只有匹配的組件會被緩存。
  • exclude: 排除的組件,以為字符串,數組,以及正則表達式,任何匹配的組件都不會被緩存,當匹配條件同時在includeexclude存在時,以exclude優先級最高。
  • max: 緩存組件的最大值,類型為字符或者數字,可以控制緩存組件的個數,一旦這個數字達到了,在新實例被創建之前,已緩存組件中最久沒有被訪問的實例會被銷毀掉。
<!-- 包含 逗號分隔字符串 -->
<keep-alive include="a,b">
    <component :is="show"></component>
</keep-alive>

<!-- 包含 正則表達式 使用v-bind -->
<keep-alive :include="/a|b/">
    <component :is="show"></component>
</keep-alive>

<!-- 包含 數組 使用v-bind -->
<keep-alive :include="['a', 'b']">
    <component :is="show"></component>
</keep-alive>

<!-- 排除 逗號分隔字符串 -->
<keep-alive exclude="a,b">
    <component :is="show"></component>
</keep-alive>

<!-- 最大緩存量 數字 -->
<keep-alive :max="10">
    <component :is="show"></component>
</keep-alive>

<keep-alive>是用在其一個直屬的子組件被開關的情形,如果在其中有v-for則不會工作,如果有上述的多個條件性的子元素,<keep-alive>要求同時只有一個子元素被渲染,通俗點說,<keep-alive>最多同時只能存在一個子組件,在<keep-alive>render函數中定義的是在渲染<keep-alive>內的組件時,Vue是取其第一個直屬子組件來進行緩存。

const vnode: VNode = getFirstComponentChild(this.$slots.default);

實現

Vue<keep-alive>組件源碼定義在dev/src/core/components/keep-alive.js,本次分析實現的commit id215f877
<keep-alive>初始化時的created階段會初始化兩個變量,分別為cachekeysmounted階段會對includeexclude變量的值做監測。

export default {
    created () {
        this.cache = Object.create(null)
        this.keys = []
    },
    mounted () {
        this.$watch('include', val => {
            pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name))
        })
    },
}

上邊的$watch方法能夠對參數的變化進行檢測,如果include或者exclude的值發生變化,就會觸發pruneCache函數,不過篩選的條件需要根據matches函數的返回值來決定,matches函數接收三種類型的參數stringRegExpArray,用以決定是否進行緩存。

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
}

pruneCache函數用以修建不符合條件的key值,每當過濾條件改變,都需要調用pruneCacheEntry方法從已有的緩存中修建不符合條件的key

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]
    if (cached && (!current || cached.tag !== current.tag)) {
        cached.componentInstance.$destroy()
    }
    cache[key] = null
    remove(keys, key)
}

在每次渲染即render時,首先獲取第一個子組件,之后便是獲取子組件的配置信息,獲取其信息,判斷該組件在渲染之前是否符合過濾條件,不需要緩存的便直接返回該組件,符合條件的直接將該組件實例從緩存中取出,並調整該組件在keys數組中的位置,將其放置於最后,如果緩存中沒有該組件,那么將其加入緩存,並且定義了max並且緩存組件數量如果超出max定義的值則將第一個緩存的vnode移除,之后返回組件並渲染。

export default {
  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    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
        // 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)
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://zhuanlan.zhihu.com/p/85120544
https://cn.vuejs.org/v2/api/#keep-alive
https://juejin.im/post/6844904082038063111
https://juejin.im/post/6844903919273918477
https://juejin.im/post/6844904099272458253
https://juejin.im/post/6844904160962281479
https://fullstackbb.com/vue/deep-into-keep-alive-in-vuejs/
https://blog.liuyunzhuge.com/2020/03/20/%E7%90%86%E8%A7%A3vue%E4%B8%ADkeep-alive%E7%BB%84%E4%BB%B6%E6%BA%90%E7%A0%81/


免責聲明!

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



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