keep-alive保持組件狀態(tài)的方法
keep-alive的設計初衷
有些業(yè)務場景需要根據(jù)不同的判斷條件,動態(tài)地在多個組件之間切換。頻繁的組件切換會導致組件反復渲染,如果組件包含有大量的邏輯和dom節(jié)點,極易造成性能問題。其次,切換后組件的狀態(tài)也會完全丟失。keep-alive的設計初衷就是為了保持組件的狀態(tài),避免組件的重復渲染。
為什么keep-alive可以直接使用
開發(fā)者無需注冊和引入,直接可以在模板中使用。 跟開發(fā)者使用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', val => { pruneCache(this, name => !matches(val, name)) }) }, render () { // keepAlive組件本身不會被渲染成dom節(jié)點,其render方法的處理邏輯的是將其包裹的組件的vnode返回 const slot = this.$slots.default // 獲取第一個組件子節(jié)點 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子類的實例),不再重新創(chuàng)建 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest // 將當前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序) 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', 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', val => { pruneCache(this, name => !matches(val, name)) }) }, render () { // keepAlive組件本身不會被渲染成dom節(jié)點,其render方法的處理邏輯的是將其包裹的組件的vnode返回 const slot = this.$slots.default // 獲取第一個組件子節(jié)點 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子類的實例),不再重新創(chuàng)建 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest // 將當前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序) 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]) } }
// 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是如何保持組件狀態(tài)的
為了保持組件狀態(tài),keep-alive設計了緩存機制。
我們知道,模板中的每個HTML標簽在vue中由相應的vnode節(jié)點對象來表示。如果是HTML標簽是組件標簽,需要為該標簽的vnode創(chuàng)建一個組件實例。組件實例負責組件內的HTML模板的編譯和渲染。因此相比于普通HTML標簽的vnode節(jié)點,組件vnode節(jié)點會存在componentOptions和 componentInstance 這兩個屬性中保存組件選項對象和組件實例的引用。
首先,我們從keep-alive組件的實現(xiàn)代碼中可以看到在組件的created鉤子中設計了緩存機制:
created () { this.cache = Object.create(null) this.keys = [] }
keep-alive設置了cache和keys兩個屬性來緩存子組件。其中cache中的每項是一個以所包裹的組件的組件名為key,包裹組件對應的vnoded為值的對象。keys的每一項是其所包裹的組件的組件名。
render 函數(shù)是vue實例和vue組件實例中用來創(chuàng)建vnode的方法。我們在實際的應用中,一般是通過template和el來指定模板,然后由vue將模板編譯成render函數(shù)。如果用戶希望能更靈活地控制vnode的創(chuàng)建可以提供自定義的render函數(shù)。
render () { const slot = this.$slots.default // 獲取第一個組件子節(jié)點 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子類的實例),不再重新創(chuàng)建 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest // 將當前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序) 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內部就是單獨提供了render函數(shù)來自定義了vnode的創(chuàng)建邏輯。首先keep-alive獲取到其所包裹的子組件的根vnode,然后去cache中查找該組件是否存在。
如果cache中不存在子組件vnode,則以{子組件名: 子組件vnode}的形式保存到cache對象中。同時將子組件名字保存到keys數(shù)組中。同時如果當前緩存的數(shù)量已經超過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", data:{tag: "component", hook: {…}}, componentOptions, componentInstance, ...}, 2::any: {tag: "vue-component-2-any", data:{tag: "component", hook: {…}}, componentOptions, componentInstance, ...}, } keys = ["1::count", "2::any"]
頁面顯示結果為:
再次點擊切換組件,切回count。此時count組件的vnode在緩存中已經存在,所以直接復用了原來count組件vnode中所保存的組件實例,組件實例中保存了原來的count值,因此組件切換完后,組件的狀態(tài)也跟著還原了回來。
下圖為count組件實例的狀態(tài),可以看到count的值得到了保持:
最終頁面顯示結果為:
從上面的分析可知,如果組件被包裹在keep-alive組件中,組件vnode會緩存到cache中。而組件的狀態(tài)又會保存在組件實例中(componentInstance),當組件再次切換回來時,keep-alive直接將之前緩存的狀態(tài)進行還原,也就實現(xiàn)了組件狀態(tài)的保持。
以上就是keep-alive保持組件狀態(tài)的方法的詳細內容,更多關于keep-alive保持組件狀態(tài)的資料請關注腳本之家其它相關文章!
- vue keep-alive實現(xiàn)多組件嵌套中個別組件存活不銷毀的操作
- vue使用keep-alive實現(xiàn)組件切換時保存原組件數(shù)據(jù)方法
- vue內置組件keep-alive事件動態(tài)緩存實例
- Vue中keep-alive組件的深入理解
- vue中keep-alive內置組件緩存的實例代碼
- Vue中keep-alive組件作用詳解
- vue keep-alive 動態(tài)刪除組件緩存的例子
- vue組件 keep-alive 和 transition 使用詳解
- vue中keep-alive組件的入門使用教程
- vue里如何主動銷毀keep-alive緩存的組件
- vue 使某個組件不被 keep-alive 緩存的方法
- vue.js內置組件之keep-alive組件使用
相關文章
vue中filters 傳入兩個參數(shù) / 使用兩個filters的實現(xiàn)方法
這篇文章主要介紹了vue中filters 傳入兩個參數(shù) / 使用兩個filters的實現(xiàn)方法,文中給大家提到了Vue 中的 filter 帶多參的使用方法,需要的朋友可以參考下2019-07-07laravel5.4+vue+element簡單搭建的示例代碼
本篇文章主要介紹了laravel5.4+vue+element簡單搭建的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08VUE項目啟動沒有問題但代碼中script標簽有藍色波浪線標注
這篇文章主要給大家介紹了關于VUE項目啟動沒有問題但代碼中script標簽有藍色波浪線標注的相關資料,文中將遇到的問題以及解決的方法介紹的非常詳細,需要的朋友可以參考下2023-05-05