1. 程式人生 > 程式設計 >keep-alive保持元件狀態的方法

keep-alive保持元件狀態的方法

keep-alive的設計初衷

有些業務場景需要根據不同的判斷條件,動態地在多個元件之間切換。頻繁的元件切換會導致元件反覆渲染,如果元件包含有大量的邏輯和dom節點,極易造成效能問題。其次,切換後元件的狀態也會完全丟失。keep-alive的設計初衷就是為了保持元件的狀態,避免元件的重複渲染。

為什麼keep-alive可以直接使用

開發者無需註冊和引入,直接可以在模板中使用。 跟開發者使用Vue.component自定義的元件不同,keep-alive無需註冊,在模板中直接可以使用,如下所示:

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

這是因為keep-alive是vue的內建元件,已經在vue中提前定義。

// core/components/keep-alive.js

export default {
 name: 'keep-alive',abstract: true,props: {
  include: patternTypes,exclude: patternTypes,max: [String,Number]
 },created () {
  this.cache = Object.create(null)
  this.keys = []
 },destroyed () {
  // keep-alive的銷燬,將所有快取的元件清除
  for (const key in this.cache) {
   pruneCacheEntry(this.cache,key,this.keys)
  }
 },mounted () {
  // 如果指定了include和exclude屬性,需要實時觀察當前這兩個屬性的變化,以及時的更新快取
  this.$watch('include',val => {
   pruneCache(this,name => matches(val,name))
  })
  this.$watch('exclude',name => !matches(val,name))
  })
 },render () {
  // keepAlive元件本身不會被渲染成dom節點,其render方法的處理邏輯的是將其包裹的元件的vnode返回
  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

   // 1、如果快取中存在該vnode,從快取中取得該元件的例項(一個元件對應一顆vnode樹,同時一個元件對應一個vue子類的例項),不再重新建立
   if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance
    // make current key freshest
    // 將當前的元件的key作為最新的快取(更新其在keys陣列中的順序)
    remove(keys,key)
    keys.push(key)
   } else {
    // 2、如果未命中快取,新增到快取
    cache[key] = vnode
    keys.push(key)
    // 如果快取超過限制,淘汰最舊的快取
    if (this.max && keys.length > parseInt(this.max)) {
     pruneCacheEntry(cache,keys[0],keys,this._vnode)
    }
   }

   // 標記為keepAlive元件
   vnode.data.keepAlive = true
  }
  return vnode || (slot && slot[0])
 }
}

這是因為keep-alive是vue的內建元件,已經在vue中提前定義。

// core/components/keep-alive.js

export default {
 name: 'keep-alive',this._vnode)
    }
   }

   // 標記為keepAlive元件
   vnode.data.keepAlive = true
  }
  return vnode || (slot && slot[0])
 }
}

// core/components/index.js
import KeepAlive from './keep-alive'

export default {
 KeepAlive
}

// core/global-api/index.js

// 匯入內建元件
import builtInComponents from '../components/index'

/**
 * 為Vue新增全域性方法和屬性
 * @param {GlobalAPI} Vue 
 */
export function initGlobalAPI (Vue: GlobalAPI) {
 
 // ...省略了無關程式碼
 
 Vue.options = Object.create(null)
 // 新增內建元件keep-alive
 extend(Vue.options.components,builtInComponents)
}

buildInComponents中包含了keep-alive的定義。在initGlobalAPI方法中,將內建元件新增到了 vue的全域性變數中。

extend(A,B)是個簡單的物件屬性複製方法。將物件B中的屬性複製到物件A中。

keep-alive是如何保持元件狀態的

為了保持元件狀態,keep-alive設計了快取機制。

我們知道,模板中的每個HTML標籤在vue中由相應的vnode節點物件來表示。如果是HTML標籤是元件標籤,需要為該標籤的vnode建立一個元件例項。元件例項負責元件內的HTML模板的編譯和渲染。因此相比於普通HTML標籤的vnode節點,元件vnode節點會存在componentOptions和 componentInstance 這兩個屬性中儲存元件選項物件和元件例項的引用。

首先,我們從keep-alive元件的實現程式碼中可以看到在元件的created鉤子中設計了快取機制:

created () {
  this.cache = Object.create(null)
  this.keys = []
}

keep-alive設定了cache和keys兩個屬性來快取子元件。其中cache中的每項是一個以所包裹的元件的元件名為key,包裹元件對應的vnoded為值的物件。keys的每一項是其所包裹的元件的元件名。

render 函式是vue例項和vue元件例項中用來建立vnode的方法。我們在實際的應用中,一般是通過template和el來指定模板,然後由vue將模板編譯成render函式。如果使用者希望能更靈活地控制vnode的建立可以提供自定義的render函式。

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,this._vnode)
    }
   }

   // 標記為keepAlive元件
   vnode.data.keepAlive = true
  }
  return vnode || (slot && slot[0])
 }

keep-alive內部就是單獨提供了render函式來自定義了vnode的建立邏輯。首先keep-alive獲取到其所包裹的子元件的根vnode,然後去cache中查詢該元件是否存在。

如果cache中不存在子元件vnode,則以{子元件名: 子元件vnode}的形式儲存到cache物件中。同時將子元件名字儲存到keys陣列中。同時如果當前快取的數量已經超過max所設定的最大值,需要淘汰掉最近最少使用的快取項(LRU)。

如果cache中存在子元件vnode,那麼只需要複用快取的元件vnode的元件例項(componentInstance)。同時需要將該子元件vnode在快取中順序調到最前面,這個主要是為了在快取不足時,正確地淘汰快取項。

舉例說明

最後通過一個例子加深一下理解。

 <div id="app">
  <keep-alive><component :is="view"></component></keep-alive>
  <button @click="view = view =='count'? 'any': 'count'">切換元件</button>
</div>
Vue.component("count",{
  data() {
    return {
      count:0
    };
  },template: "<div @click='count+=1'>點了我 {{count}} 次</div>"
});
  
Vue.component("any",{
  template: "<div>any</div>"
});

new Vue({
  el: "#app",data: {
   view: "count"
  }
});

由於view預設值是count,因此keep-alive包裹的子元件是count。此時keep-alive的快取中為空,因此會把元件count的vnode新增到快取。快取結果為

cache = {1::count: {tag: "vue-component-1-count",data:{tag: "component",hook: {…}}},componentOptions,componentInstance,...}
keys = ["1::count"]

keep-alive保持元件狀態的方法

點選一下元件count,元件的顯示內容變成"點了我1次",然後切換到元件any。與count元件相同,由於在keep-alive的快取中還未儲存any元件的vnode,因此需要將any新增到快取中。此時快取結果變成了:

cache = {
  1::count: {tag: "vue-component-1-count",hook: {…}},...},2::any: {tag: "vue-component-2-any",}
keys = ["1::count","2::any"]

頁面顯示結果為:

keep-alive保持元件狀態的方法

再次點選切換元件,切回count。此時count元件的vnode在快取中已經存在,所以直接複用了原來count元件vnode中所儲存的元件例項,元件例項中儲存了原來的count值,因此元件切換完後,元件的狀態也跟著還原了回來。

下圖為count元件例項的狀態,可以看到count的值得到了保持:

keep-alive保持元件狀態的方法

最終頁面顯示結果為:

keep-alive保持元件狀態的方法

從上面的分析可知,如果元件被包裹在keep-alive元件中,元件vnode會快取到cache中。而元件的狀態又會儲存在元件例項中(componentInstance),當元件再次切換回來時,keep-alive直接將之前快取的狀態進行還原,也就實現了元件狀態的保持。

以上就是keep-alive保持元件狀態的方法的詳細內容,更多關於keep-alive保持元件狀態的資料請關注我們其它相關文章!