keep-alive 組件級緩存

前言

      在Vue構建的單頁面應用(SPA)中,路由模塊通常使用vue-routervue-router不保存被切換組件的狀態,vue

它進行push或者replace時,舊組件會被銷燬,而新組件會被新建,走一遍完整的生命週期。vue-router

但有時候,咱們有一些需求,好比跳轉到詳情頁面時,須要保持列表頁的滾動條的深度,等返回的時候依然在這個位置,這樣能夠提升用戶體驗。數組

在Vue中,對於這種「頁面緩存」的需求,咱們可使用keep-alive組件來解決這個需求。緩存

keep-alive

keep-alive是個抽象組件(或稱爲功能型組件),實際上不會被渲染在DOM樹中。
它的做用是在內存中緩存組件(不讓組件銷燬),等到下次再渲染的時候,還會保持其中的全部狀態,而且會觸發activated鉤子函數。
由於緩存的須要一般出如今頁面切換時,因此常與router-view一塊兒出現:
<keep-alive>
    <router-view />
</keep-alive>
可使用keep-alive組件的 include/exclude屬性。
include屬性表示要緩存的組件名(即組件定義時的name屬性),
接收的類型爲 stringRegExpstring數組;
exclude屬性有着相反的做用,匹配到的組件不會被緩存。
假如可能出如今同一router-view的N個頁面中,我只想緩存列表頁和詳情頁,那麼能夠這樣寫:
<keep-alive :include="['Home', 'User']">
  <router-view />
</keep-alive>

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

但願實現前進刷新、後退不刷新的效果。即加載過的界面能緩存起來(返回不用從新加載),關閉的界面能被銷燬掉(再進入時從新加載)。app

例如對a->b->c 前進(b,c)刷新,c->b->a 後退(b,a)不刷新函數

知道路由是前進仍是後退就行了,ui

這樣的話我就能在後退的時候讓from路由的keepAlive置爲false this

to路由的keepAlive置爲ture,就能在再次前進時,從新加載以前這個keepAlive被置爲false的路由了spa

可是這個須要集合鈎子函數來是實現code

// 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>

下面在router/index.js即咱們的路由文件中,定義meta信息:

// list是咱們的搜索結果頁面
{      
    path: '/list',  
    name: 'List',      
    component: resolve => require(['@/pages/list'], resolve),    
    meta: {        
        isUseCache: false,  // 這個字段的意思稍後再說      
        keepAlive: true  // 經過此字段判斷是否須要緩存當前組件  
    }    
}

 

說這以前,先簡單說一下和緩存相關的vue鉤子函數

設置了keepAlive緩存的組件:

       第一次進入:beforeRouterEnter ->created->…->activated->…->deactivated

       後續進入時:beforeRouterEnter ->activated->deactivated

 

能夠看出,只有第一次進入該組件時,纔會走created鉤子,而須要緩存的組件中activated是每次都會走的鉤子函數。

因此,咱們要在這個鉤子裏面去判斷,當前組件是須要使用緩存的數據仍是從新刷新獲取數據。思路有了,下面咱們來實現

// list組價的activated鉤子
 activated() {
    // isUseCache爲false時才從新刷新獲取數據
    // 由於對list使用keep-alive來緩存組件,因此默認是會使用緩存數據的         
    if(!this.$route.meta.isUseCache){            
        this.list = []; // 清空原有數據
        this.onLoad(); // 這是咱們獲取數據的函數
    } 
}

這裏的isUseCache 其實就是咱們用來判斷是否須要使用緩存數據的字段,咱們在list的路由的meta中已經默認設置爲false,因此第一次進入list時是獲取數據的。

當咱們從詳情頁返回時,咱們把list頁面路由的isUseCache設置成true,這樣咱們在返回list頁面時會使用緩存數據

// 詳情頁面的beforeRouteLeave鉤子函數
beforeRouteLeave (to, from, next) {        
    if (to.name == 'List') {
        to.meta.isUseCache = true;    
    }        
    next();
}

咱們這裏是在即將離開detail頁面前判斷是否返回的列表頁。

若是是返回list頁面,則把list頁面路由的isUseCache字段設置成true。爲何這樣設置呢?

由於咱們對list組件使用的keep-alive進行緩存組件,其默認就是使用緩存的。

而咱們又在list組件的actived鉤子函數中進行了判斷:

   只有在list頁面的isUseCache==false時纔會清空原有數據並從新獲取數據。

  因此此處設置isUseCache爲true,此時返會list頁面是不會從新獲取數據的,而是使用的緩存數據。

  detail返回list能夠緩存數據了,那麼search前往list頁面時怎麼讓list頁面不使用緩存數據而是獲取新數據呢?咱們從新回到list的activated鉤子中:

// list組價的activated鉤子
 activated() {
    // isUseCache爲false時才從新刷新獲取數據
    // 由於對list使用keep-alive來緩存組件,因此默認是會使用緩存數據的         
    if(!this.$route.meta.isUseCache){            
        this.list = []; // 清空原有數據
        this.onLoad(); // 這是咱們獲取數據的函數
        this.$route.meta.isUseCache = false;    } 
}

咱們加了一行this.$route.meta.isUseCache=false;也就是從detail返回list後,將list的isUseCache字段爲false

而從detail返回list前,咱們設置了list的isUseCache爲true。

因此,只有從detail返回list才使用緩存數據,而其餘頁面進入list是從新刷新數據的。

至此,一個前進刷新、後退返回的功能基本完成了

場景還原實際

好比,若是這個詳情頁是個訂單詳情,那麼在訂單詳情頁可能會有刪除訂單的操做。
那麼刪除訂單操做後會返回訂單列表頁,是須要列表頁從新刷新的。
那麼咱們須要此時在訂單詳情頁進行是否要刷新的判斷。簡單改造一下詳情頁:
data () {    
    return {
        isDel: false  // 是否進行了刪除訂單的操做       
    }
},
beforeRouteLeave (to, from, next) {        
    if (to.name == 'List') {
        // 根據是否刪除了訂單的狀態,進行判斷list是否須要使用緩存數據
        to.meta.isUseCache = !this.isDel;                
    }        
    next();    
},
methods: {        
    deleteOrder () {       
        // 這裏是一些刪除訂單的操做

        // 將狀態變爲已刪除訂單
        // 因此beforeRouteLeave鉤子中就會將list組件路由的isUseCache設置爲false    
        // 因此此時再返回list時,list是會從新刷新數據的 
        this.isDel = true; 
        this.$router.go(-1)
    }
}

 

另外用Vuex來實現後退功能

然後在一篇博客中看到是用Vuex來寫的,因此這邊也本身demo了下:

就是下面的代碼了:

 

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

只須要將B動態地從include數組中增長/刪除就好了

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

 

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)
    }
  }
}

   在父頁面中定義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
    })
  }
}

 緩存:在路由配置頁中,約定使用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)
  }
})

取消緩存的時機:對緩存組件使用路由的組件層鉤子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中,這樣不至於讓組件之間的跳轉關係變得混亂。

一個須要注意的細節:由於keep-alive組件的include數組操做的對象是組件名、而不是路由名,

所以咱們定義每個組件時,都要顯式聲明name屬性,不然緩存不起做用。並且,一個顯式的name對Vue devtools有提示做用。

相關文章
相關標籤/搜索