vue keep-alive 和vue-router 控制組件緩存

寫在開頭

終極解決方法柜去,直接看文章 解決循環(huán)跳轉(zhuǎn)頁面不刷新 部分

瀏覽器前進(jìn)刷新后退不刷新

這一部分用來祭奠我曾經(jīng)掉落在地的幾根頭發(fā)和逝去的時(shí)間灰嫉。

  • 思路:

    1. vue 頁面瀏覽記錄后退的能力是通過插件 vue-router 實(shí)現(xiàn)的。而 vue-rotuer 通過history.pushState 方法增加瀏覽記錄(或者history.replaceState)诡蜓,然后通過監(jiān)聽 popstate 事件來監(jiān)聽瀏覽器當(dāng)前會(huì)話頁面后退行為熬甫。
    2. vue-rotuer 調(diào)用history.pushState 的時(shí)候會(huì)傳入一個(gè){key: _key /*唯一值*/} 對(duì)象,而捕獲 popstate 事件時(shí)蔓罚,可以通過 e.state.key 獲取這個(gè)唯一值
    3. 基于以上兩點(diǎn),我們可以通過收集 key 值來組成頁面之間的跳轉(zhuǎn)記錄瞻颂,然后通過 popstate 事件的key值來判斷頁面是前進(jìn)還是后退豺谈。頁面前進(jìn)時(shí),直接使用 keep-alive 組件緩存當(dāng)前頁面贡这;如果頁面后退茬末,那么在回到上一個(gè)頁面之前從 keep-alive 中清除當(dāng)前頁面緩存
  • 實(shí)現(xiàn)代碼

    <!-- 組件 keep-previous-alive 代碼 -->
    
    <template>
      <keep-alive>
        <slot />
      </keep-alive>
    </template>
    
    <script>
    let routerHistory = [];
    
    function remove(arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item);
        if (index > -1) {
          return arr.splice(index, 1);
        }
      }
    }
    
    // 清除 keep-alive 中的緩存
    function pruneCacheEntry(cache, key, keys) {
      const cached = cache[key];
      if (cached) {
        cached.componentInstance.$destroy();
      }
      cache[key] = null;
      remove(keys, key);
    }
    
    function handleAfterEachRouter(to, from) {
      const historyState = window.history.state || {};
      const historyStateKey = historyState.key;
      const index = routerHistory.indexOf(historyStateKey);
    
      if (index > -1) {
        // 回到舊頁面
        routerHistory.splice(index + 1);
      } else {
        // 進(jìn)入新頁面
        routerHistory.push(historyStateKey);
      }
    }
    
    function handleBeforeEachRouter(to, from, next) {
      // setTimeout 確保 handlePopstate 方法的調(diào)用是在進(jìn)入下個(gè)頁面之前
      setTimeout(() => {
        next();
      }, 0);
    }
    
    function setup(com) {
      const router = com.$router;
    
      if (router.beforeHooks.indexOf(handleBeforeEachRouter) <= -1) {
        router.beforeEach(handleBeforeEachRouter);
      }
    
      if (router.afterHooks.indexOf(handleAfterEachRouter) <= -1) {
        router.afterEach(handleAfterEachRouter);
      }
    }
    
    export default {
      name: 'keep-previous-alive',
      components: {},
      props: {},
      data() {
        return {
          keepAliveVnode: null,
        };
      },
      methods: {
        handlePopstate(e) {
          const state = e.state || {};
          const stateKey = state.key;
          const vnode = this.$slots.default && this.$slots.default[0];
    
          if (!vnode) return;
          if (stateKey === undefined) return;
          if (routerHistory.indexOf(stateKey) <= -1) return;
    
          // history back
          const keepAliveInstance = this.keepAliveVnode.componentInstance;
          const componentOptions = vnode.componentOptions;
          const key =
            vnode.key == null
              ? componentOptions.Ctor.cid +
                (componentOptions.tag ? `::${componentOptions.tag}` : "")
              : vnode.key;
          pruneCacheEntry(keepAliveInstance.cache, key, keepAliveInstance.keys);
        },
      },
      created() {
        window.addEventListener("popstate", this.handlePopstate);
        window.vm = this;
      },
      mounted() {
        this.keepAliveVnode = this._vnode;
        setup(this);
      },
      beforeDestroy() {
        window.removeEventListener("popstate", this.handlePopstate);
      },
    };
    </script>
    
    <style lang="scss" scoped>
    </style>
    
  • 使用方法

    <template>
      <div>
        <KeepPreviousAlive>
          <router-view v-if="$route.matched.some((r) => r.meta.keepAlive)"></router-view>
        </KeepPreviousAlive>
        <router-view v-if="!$route.matched.some((r) => r.meta.keepAlive)"></router-view>
      </div>
    </template>
    
  • 優(yōu)點(diǎn)和缺陷

    • 配置簡單:只要使用KeepPreviousAlive 組件就可以,不用在緩存頁面的 beforeRouteEnter 守衛(wèi)方法中判斷是否需要重新創(chuàng)建頁面盖矫。
    • 缺點(diǎn):存在循環(huán)跳轉(zhuǎn)時(shí)丽惭,獲取到頁面數(shù)據(jù)都是緩存中的,不會(huì)重新刷新頁面辈双。例如:
      1. 用戶在 A 頁面责掏,調(diào)用 router.push('/b') 跳轉(zhuǎn)到 B 頁面
      2. 用戶已經(jīng)跳到 B 頁面,然后調(diào)用 router.push('/a') 跳轉(zhuǎn)到 A 頁面
      3. 這時(shí)候 A 頁面的數(shù)據(jù)還是從緩存中取到湃望,并不會(huì)進(jìn)入創(chuàng)建頁面的流程换衬。

<h3 id="3">解決循環(huán)跳轉(zhuǎn)頁面不刷新</h3>

在介紹解決方法之前,讓我們一起忘掉上一部分關(guān)于 KeepPreviousAlive 組件的實(shí)現(xiàn)证芭,來看下面一段代碼瞳浦。

// keep-alive 部分源碼
const key = vnode.key == null
    ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "")
    : vnode.key;

pruneCacheEntry(keepAliveInstance.cache, key, keepAliveInstance.keys);

從上面的代碼中可以知道 keep-alive 緩存的 key 值可以從 vnode.key 中獲取,而 vnode.key 是通過 v-bind:key="xxx" 傳入的废士,因此我們可以自己控制組件緩存的 key 值叫潦,那么當(dāng)我們需要讓某個(gè)已經(jīng)緩存的組件重新進(jìn)入創(chuàng)建頁面的時(shí)候,可以改變這個(gè)組件的 key 值官硝,讓它命中不了緩存矗蕊。

代碼示例:

<template>
  <div>
    <!-- 給緩存設(shè)置最大個(gè)數(shù)四敞,避免占用太多內(nèi)存 -->
    <keep-alive :max="20">
      <!-- 傳入緩存 key 值 -->
      <router-view :key="$route.fullPath" v-if="$route.matched.some((r) => r.meta.keepAlive)" />  
    </keep-alive>
    <router-view v-if="!$route.matched.some((r) => r.meta.keepAlive)" />
  </div>
</template>
// 其他頁面
// 進(jìn)入a頁面時(shí),a頁面需要重新創(chuàng)建
this.$router.push({
  path: '/a',
  query: {
    q: 'random querystring'
  }
})
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拔妥,一起剝皮案震驚了整個(gè)濱河市忿危,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌没龙,老刑警劉巖铺厨,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異硬纤,居然都是意外死亡解滓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門筝家,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洼裤,“玉大人,你說我怎么就攤上這事溪王∪埃” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵莹菱,是天一觀的道長移国。 經(jīng)常有香客問我,道長道伟,這世上最難降的妖魔是什么迹缀? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜜徽,結(jié)果婚禮上祝懂,老公的妹妹穿的比我還像新娘。我一直安慰自己拘鞋,他們只是感情好砚蓬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掐禁,像睡著了一般怜械。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上傅事,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天缕允,我揣著相機(jī)與錄音,去河邊找鬼蹭越。 笑死障本,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驾霜,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼案训,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了粪糙?” 一聲冷哼從身側(cè)響起强霎,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蓉冈,沒想到半個(gè)月后城舞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寞酿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年家夺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伐弹。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拉馋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惨好,到底是詐尸還是另有隱情煌茴,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布昧狮,位于F島的核電站景馁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逗鸣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一绰精、第九天 我趴在偏房一處隱蔽的房頂上張望撒璧。 院中可真熱鬧,春花似錦笨使、人聲如沸卿樱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽繁调。三九已至,卻和暖如春靶草,著一層夾襖步出監(jiān)牢的瞬間蹄胰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國打工奕翔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裕寨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像宾袜,于是被迫代替她去往敵國和親捻艳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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