keep-alive:組件級緩存


title: keep-alive:組件級緩存 tags:html

  • keep-alive
  • Vue
  • vue-router

頁面緩存

在Vue構建的單頁面應用(SPA)中,路由模塊通常使用vue-router。vue-router不保存被切換組件的狀態,它進行push或者replace時,舊組件會被銷燬,而新組件會被新建,走一遍完整的生命週期。vue

但有時候,咱們有一些需求,好比跳轉到詳情頁面時,須要保持列表頁的滾動條的深度,等返回的時候依然在這個位置,這樣能夠提升用戶體驗。在Vue中,對於這種「頁面緩存」的需求,咱們可使用keep-alive組件來解決這個需求。git

使用方式

keep-alive是個抽象組件(或稱爲功能型組件),實際上不會被渲染在DOM樹中。它的做用是在內存中緩存組件(不讓組件銷燬),等到下次再渲染的時候,還會保持其中的全部狀態,而且會觸發activated鉤子函數。由於緩存的須要一般出如今頁面切換時,因此常與router-view一塊兒出現:github

<keep-alive>
	<router-view />
</keep-alive>
複製代碼

如此一來,每個在router-view中渲染的組件,都會被緩存起來。vue-router

若是隻想渲染某一些頁面/組件,可使用keep-alive組件的include/exclude屬性。include屬性表示要緩存的組件名(即組件定義時的name屬性),接收的類型爲string、RegExp或string數組;exclude屬性有着相反的做用,匹配到的組件不會被緩存。假如可能出如今同一router-view的N個頁面中,我只想緩存列表頁和詳情頁,那麼能夠這樣寫:api

<keep-alive :include="['ListView', 'DetailView']">
  <router-view />
</keep-alive>
複製代碼

實現條件緩存:全局的include數組

上面include的寫法不是經常使用的,由於它固定了哪幾個頁面緩存或不緩存,假若有下面這個場景:數組

  • 現有頁面:首頁(A)、列表頁(B)、詳情頁(C),通常能夠從:A->B->C;
  • B到C再返回B時,B要保持列表滾動的距離;
  • B返回A再進入B時,B不須要保持狀態,是全新的。

很明顯,這個例子中,B是「條件緩存」的,C->B時保持緩存,A->B時放棄緩存。其實解決方案也不難,只須要將B動態地從include數組中增長/刪除就好了。具體步驟是:緩存

  1. 在Vuex中定義一個全局的緩存數組,待傳給include:
// global.js

export default {
  namespaced: true,
  state: {
    keepAliveComponents: [] // 緩存數組
  },
  mutations: {
    keepAlive (state, component) {
      // 注:防止重複添加(固然也可使用Set)
      !state.keepAliveComponents.includes(component) && 
        state.keepAliveComponents.push(component)
    },
    noKeepAlive (state, component) {
      const index = state.keepAliveComponents.indexOf(component)
      index !== -1 &&
        state.keepAliveComponents.splice(index, 1)
    }
  }
}
複製代碼
  1. 在父頁面中定義keep-alive,並傳入全局的緩存數組:
// App.vue

<div class="app">
  <!--傳入include數組-->
  <keep-alive :include="keepAliveComponents">
    <router-view></router-view>
  </keep-alive>
</div>

export default {
  computed: {
    ...mapState({
      keepAliveComponents: state => state.global.keepAliveComponents
    })
  }
}
複製代碼
  1. 緩存:在路由配置頁中,約定使用meta屬性keepAlive,值爲true表示組件須要緩存。在全局路由鉤子beforeEach中對該屬性進行處理,這樣一來,每次進入該組件,都進行緩存:
const router = new Router({
  routes: [
    {
      path: '/A/B',
      name: 'B',
      component: B,
      meta: {
        title: 'B頁面',
        keepAlive: true // 這裏指定B組件的緩存性
      }
    }
  ]
})

router.beforeEach((to, from, next) => {
  // 在路由全局鉤子beforeEach中,根據keepAlive屬性,統一設置頁面的緩存性
  // 做用是每次進入該組件,就將它緩存
  if (to.meta.keepAlive) {
    store.commit('global/keepAlive', to.name)
  }
})
複製代碼
  1. 取消緩存的時機:對緩存組件使用路由的組件層鉤子beforeRouteLeave。由於B->A->B時不須要緩存B,因此能夠認爲:當B的下一個頁面不是C時取消B的緩存,那麼下次進入B組件時B就是全新的:
export default {
  name: 'B',
  created () {
      // ...設置滾動條在最頂部
  },
  beforeRouteLeave (to, from, next) {
    // 若是下一個頁面不是詳情頁(C),則取消列表頁(B)的緩存
    if (to.name !== 'C') {
        this.$store.commit('global/noKeepAlive', from.name)
    }
    next()
  }
}
複製代碼

由於B的條件緩存,是B本身的職責,因此最好把該業務邏輯寫在B的內部,而不是A中,這樣不至於讓組件之間的跳轉關係變得混亂。app

  1. 一個須要注意的細節:由於keep-alive組件的include數組操做的對象是組件名、而不是路由名,所以咱們定義每個組件時,都要顯式聲明name屬性,不然緩存不起做用。並且,一個顯式的name對Vue devtools有提示做用。

另外一種方式?

網上看到實現條件緩存的另外一種方式很相似,但它是在父組件中,使用兩個router-view並進行條件渲染:async

// App.vue

<div class="app">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
複製代碼

在GitHub的issue中(見參考連接)有一些爭議,本人也尚未比較全面的驗證過,因此也暫不知道效果如何。

Q&A

Q:組件名已經加入全局include數組了,爲何頁面仍是沒有緩存到?

A:若是按照上面的思路來作就沒有問題,注意第5點,即頗有可能你忘記給組件聲明一個name屬性了。

Q:組件可以緩存,可是滾動條並無緩存、好比仍是會回到頂部?

A:滾動條這個問題跟組件的HTML結構有關。淺顯而言,keep-alive功能緩存的是父元素相對於組件的scrollTop,因此若是你的組件/頁面設置了height:100%、滾動條在組件內部的,看到的滾動條就是沒有緩存的。固然,關於這個,還有待進入源碼深究,給本身留個坑吧。

參考

  • 文檔

在動態組件上使用-keep-alive

API:keep-alive

  • 應用

vue-router 之 keep-alive

vue實現前進刷新,後退不刷新

  • 討論

https://github.com/vuejs/vue-router/issues/811

相關文章
相關標籤/搜索