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"]
點選一下元件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"]
頁面顯示結果為:
再次點選切換元件,切回count。此時count元件的vnode在快取中已經存在,所以直接複用了原來count元件vnode中所儲存的元件例項,元件例項中儲存了原來的count值,因此元件切換完後,元件的狀態也跟著還原了回來。
下圖為count元件例項的狀態,可以看到count的值得到了保持:
最終頁面顯示結果為:
從上面的分析可知,如果元件被包裹在keep-alive元件中,元件vnode會快取到cache中。而元件的狀態又會儲存在元件例項中(componentInstance),當元件再次切換回來時,keep-alive直接將之前快取的狀態進行還原,也就實現了元件狀態的保持。
以上就是keep-alive保持元件狀態的方法的詳細內容,更多關於keep-alive保持元件狀態的資料請關注我們其它相關文章!