vue列表緩存keep-alive

使用場景:

列表查詢出約課記錄之后五垮,跳轉(zhuǎn)到詳情后再返回頁面內(nèi)容刷新扎附,之前搜索的結(jié)果就沒有了抠忘,為了解決這一問題撩炊,使用了keep-alive組件來實(shí)現(xiàn)頁面緩存。

實(shí)現(xiàn)方案:

keep-alive

實(shí)現(xiàn)原理:

  • keep-alive是vue2.0提供的用來緩存的內(nèi)置組件崎脉,避免多次加載相同的組建拧咳,減少性能消耗。(keep-alive.js)
  • 將需要緩存的VNode節(jié)點(diǎn)保存在this.cache中(而不是直接存儲(chǔ)DOM結(jié)構(gòu))囚灼,在render時(shí)骆膝,如果VNode的name符合緩存條件,則直接從this.cache中取出緩存的VNode實(shí)例進(jìn)行渲染灶体。
  • keep-alive的渲染是在patch階段阅签,在已經(jīng)緩存的情況下不會(huì)進(jìn)入$mount階段,所以mounted之前的鉤子只會(huì)執(zhí)行一次蝎抽。

keep-alive.js源碼:

// src/core/components/keep-alive.js
export default {
    name: 'keep-alive',
    abstract: true, // 判斷當(dāng)前組件虛擬dom是否渲染成真實(shí)dom的關(guān)鍵
    props: {
        include: patternTypes, // 緩存白名單
        exclude: patternTypes, // 緩存黑名單
        max: [String, Number] // 緩存的組件
    },
    created() {
        this.cache = Object.create(null) // 緩存虛擬dom
        this.keys = [] // 緩存的虛擬dom的鍵集合
    },
    destroyed() {
        for (const key in this.cache) {
            // 刪除所有的緩存
            pruneCacheEntry(this.cache, key, this.keys)
        }
    },
    mounted() {
        // 實(shí)時(shí)監(jiān)聽黑白名單的變動(dòng)
        this.$watch('include', val => {
            pruneCache(this, name => matched(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name))
        })
    },
    render() {
        // 先省略...
    }
}

render函數(shù)解讀

  • 通過getFirstComponentChild獲取第一個(gè)組件(vnode)愉择;
  • 獲取該組件的name(有name的返回name,無name返回標(biāo)簽名)织中;
  • 將這個(gè)name通過include、exclude屬性進(jìn)行匹配衷戈;
    • 匹配不成功說明不需要緩存狭吼,直接返回vnode;
    • 匹配成功后殖妇,根據(jù)key在this.cache中查找是否已經(jīng)被緩存過刁笙;
      • 如果已緩存過,將緩存的VNode的組件實(shí)例componentInsance覆蓋到當(dāng)前vnode上谦趣,并返回疲吸;
      • 未被緩存則將VNode存儲(chǔ)在this.cache中;
render () {
    /* 得到slot插槽中的第一個(gè)組件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
        /* 獲取組件名稱前鹅,優(yōu)先獲取組件的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
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
        /* 如果已經(jīng)做過緩存了則直接從緩存中獲取組件實(shí)例給vnode,還未緩存過則進(jìn)行緩存 */
        if (this.cache[key]) {
            vnode.componentInstance = this.cache[key].componentInstance
        } else {
            this.cache[key] = vnode
        }
        /* keepAlive標(biāo)記位 */
        vnode.data.keepAlive = true
    }
    return vnode
}

渲染階段

只執(zhí)行一次的鉤子:當(dāng)vnode.componentInstance和keepAlive為true時(shí)舰绘,不再進(jìn)入$mount過程蹂喻,也就不會(huì)執(zhí)行mounted以及之前的鉤子函數(shù)(beforeCreated、created捂寿、mounted)

// src/core/vdom/create-component.js
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }
  // ...
}

可重復(fù)執(zhí)行的activated:在patch階段口四,最后會(huì)執(zhí)行invokeInsertHook函數(shù),這個(gè)函數(shù)調(diào)用組件實(shí)例的insert鉤子秦陋,insert鉤子中調(diào)用了activateChildComponent函數(shù)蔓彩,遞歸執(zhí)行子組件中的activated鉤子函數(shù)

// src/core/vdom/patch.js
function invokeInsertHook (vnode, queue, initial) {
    if (isTrue(initial) && isDef(vnode.parent)) {
      vnode.parent.data.pendingInsert = queue
    } else {
      for (let i = 0; i < queue.length; ++i) {
        queue[i].data.hook.insert(queue[i]) // 調(diào)用VNode自身的insert鉤子函數(shù)
      }
    }
}
// src/core/vdom/create-component.js
const componentVNodeHooks = {
  // init()
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  // ...
}

頁面緩存的使用方法:

  1. keep-alive組件提供了兩個(gè)屬性include、exclude,可以用逗號隔開的字符串或正則表達(dá)式赤嚼、一個(gè)數(shù)組來表示旷赖。
  2. keep-alive的生命周期函數(shù)created、mounted只有創(chuàng)建時(shí)會(huì)被觸發(fā)一次
  3. 生命周期鉤子有兩個(gè)activated探膊、deactivated杠愧,分別在組件激活、非激活狀態(tài)時(shí)觸發(fā)
  4. 因?yàn)閗eep-alive將組件緩存起來逞壁,不會(huì)被銷毀和重建流济,所以不會(huì)重新調(diào)用created、mounted方法腌闯。
  5. 需要重置data數(shù)據(jù)Object.assign(this.data, this.options.data.call(this))
  6. 從指定路由跳轉(zhuǎn)回來需要刷新的情況绳瘟,可以結(jié)合路由守衛(wèi)beforeRouterEnter和beforeRouterLeave來區(qū)分是否需要刷新
  7. 使用vue-devtool觀察組件的緩存狀態(tài),灰色的為被緩存起來的狀態(tài)

pageList → pageDetail → pageList姿骏,pageList保存原有狀態(tài)糖声;pageList → 其他 → pageList,pageList初始化狀態(tài)

使用方法一

// 使用router的meta屬性控制
<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta || !$route.meta.keepAlive" class="main"></router-view>
// router.js
{
    path: 'pageList',
    name: 'pageList',
    component: () => import('../pages/pageList.vue'),
    meta: {
        // keepAlive是否使用keep-alive組件分瘦,isUseCache是否需要緩存
        keepAlive: true, isUseCache: false
    }
}
// pageList.vue
<template>
  <div></div>
</template>
<script>
export default {
    name: 'pageList',
    data() {
        return {}
    },
    activated() {
        // 組件活動(dòng)狀態(tài)
        if (!this.$route.meta.isUseCache) {
            // 不需要緩存時(shí)需要重置數(shù)據(jù)
            Object.assign(this.$data, this.$options.data.call(this))
            // 設(shè)置為需要緩存
            this.$route.meta.isUseCache = true
        }
    },
    deactivated() {
        // 組件非活躍狀態(tài)
    },
    beforeRouteEnter(to, from, next) {
        // 從pageA頁面跳轉(zhuǎn)到pageB需要緩存蘸泻,其他頁面跳轉(zhuǎn)會(huì)pageB不需要緩存
        if(from.name != 'pageDetail'){
            to.meta.isUseCache = false;
        } else {
            to.meta.isUseCache = true
        }
    }, 
}
</script>

pageList → pageDetail → pageList,pageList保存原有狀態(tài)嘲玫;pageList → 其他 → pageList悦施,pageList初始化狀態(tài)

使用方法二:

// 使用keep-alive的prop屬性
// cacheList可以存儲(chǔ)在vuex中,默認(rèn)為'pageList'
<keep-alive :include="cacheList">
    <router-view></router-view>
</keep-alive>
// pageList.vue
    beforeRouteLeave (to, from, next) {
        if (to.name !== 'pageDetail') {
            this.$store.dispatch('setCacheList', '')
        }else {
            this.$store.dispatch('setCacheList', 'pageList')
        }
        next()
    }

問題:方法二中,pageList → pageDetail → 其他 → pageList去团,pageList采用了緩存的數(shù)據(jù)抡诞, 解決:其他頁面中的beforeRouteLeave增加 this.$store.dispatch('setCacheList', '')

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市土陪,隨后出現(xiàn)的幾起案子昼汗,更是在濱河造成了極大的恐慌,老刑警劉巖鬼雀,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顷窒,死亡現(xiàn)場離奇詭異,居然都是意外死亡取刃,警方通過查閱死者的電腦和手機(jī)蹋肮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璧疗,“玉大人坯辩,你說我怎么就攤上這事”老溃” “怎么了漆魔?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我改抡,道長矢炼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任阿纤,我火速辦了婚禮句灌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欠拾。我一直安慰自己胰锌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布藐窄。 她就那樣靜靜地躺著资昧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荆忍。 梳的紋絲不亂的頭發(fā)上格带,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機(jī)與錄音刹枉,去河邊找鬼叽唱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛微宝,可吹牛的內(nèi)容都是我干的尔觉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼芥吟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了专甩?” 一聲冷哼從身側(cè)響起钟鸵,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涤躲,沒想到半個(gè)月后棺耍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡种樱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年蒙袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫩挤。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡害幅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岂昭,到底是詐尸還是另有隱情以现,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站邑遏,受9級特大地震影響佣赖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜记盒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一憎蛤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纪吮,春花似錦俩檬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巷疼,卻和暖如春晚胡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嚼沿。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工估盘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骡尽。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓遣妥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親攀细。 傳聞我的和親對象是個(gè)殘疾皇子箫踩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 一、前言 本文介紹的內(nèi)容包括: keep-alive用法:動(dòng)態(tài)組件&vue-router keep-alive源碼...
    amCow閱讀 165,316評論 5 132
  • 1. 概念 keep-alive是vue內(nèi)置的一個(gè)組件谭贪,可以使被包含的組件保留狀態(tài)境钟,或避免重新渲染。它自身并不會(huì)渲...
    意切閱讀 883評論 0 0
  • 官方定義 keep-alive 是 Vue 的內(nèi)置組件俭识,當(dāng)它包裹動(dòng)態(tài)組件時(shí)慨削,會(huì)緩存不活動(dòng)的組件實(shí)例,而不是銷毀它們...
    書蟲和泰迪熊閱讀 110評論 0 1
  • keep-alive是Vue.js的一個(gè)內(nèi)置組件套媚。它能夠不活動(dòng)的組件實(shí)例保存在內(nèi)存中缚态,我們來探究一下它的源碼實(shí)現(xiàn)。...
    zhongmeizhi閱讀 2,290評論 0 1
  • 一堤瘤、前言 本文介紹的內(nèi)容包括: keep-alive用法:動(dòng)態(tài)組件&vue-router keep-alive源碼...
    乙哥驛站閱讀 309評論 0 1