Vue keep-alive实践

1,127 阅读2分钟

SPA应用的问题

我们来看个例子:

  1. 商品列表根据商品品牌进行搜索

image

  1. 点击详情按钮进入商品详情页面

  2. 点击详情页面的返回按钮,如下图所属,搜索项消失了,重新展示了所有的商品

image

在SPA应用中我们很少使用新窗口打开,那么就存在一个问题,页面返回的时候保持上次页面的状态

解决方案 keep-alive

keep-alive介绍

是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

keep-alive接收的参数:

include - 字符串或正则表达式。只有名称匹配的组件会被缓存。

exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。

max - 数字。最多可以缓存多少组件实例。

keep-alive详细介绍参见官网地址:cn.vuejs.org/v2/api/#kee…

keep-alive使用

网络上常见的处理方案(有问题)

App.vue

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

keeplist.vue

beforeRouteLeave (to, from, next) {
    from.meta.keepAlive = to.name === 'keepdetail'
    next()
}

router.js

{
    path: 'keeplist',
    name: 'keeplist',
    meta: {
        keepAlive: true
    },
    component: () => import('./views/keeplist')
}

问题:

第一次操作home--> keeplist---> keepdetail的时候是理想效果,但当第二次操作返回home后,进行home--> keeplist--> keepdetail--> keeplist时,缓存数据还在。如下图

image

分析:

第一次进入keeplist页面,因为默认的meta.keepAlive = true所以走的是

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>

当keeplist返回到home的时候,由于to.name != 'keepdetail',所以meta.keepAlive设置成了false,再次进入keeplist页面的时候走的是

<router-view v-if="!$route.meta.keepAlive"></router-view>

当keeplist跳转到keepdetail的时候,meta.keepAlive设置成了true,从keepdetail返回keeplist的时候,走的是

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>

这个时候唤起的是原来缓存的那个页面,所以第二次返回的时候有问题

从keep-alive的属性值看使用方案(实现方案)

keep-alive其中两个属性:include,exclude

include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。

官方示例

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>
  1. 在vuex中管理include和exclude
export default new Vuex.Store({
  state: {
    keepInclude: [],
    keepExclude: []
  },
  mutations: {
    [ADD_KEEP_INCLUDE] (state, payload) {
      // TODO 添加缓存name
      state.keepInclude = state.keepInclude.filter(item => item !== payload)
      state.keepInclude.push(payload)
      console.log(state.keepInclude)
    },
    [REMOVE_KEEP_INCLUDE] (state, payload) {
      // TODO  匹配所有要缓存的name 不包含 payload
      state.keepInclude = state.keepInclude.filter(item => item !== payload)
    },
    [ADD_KEEP_EXCLUDE] (state, payload) {
      // TODO  添加不要缓存的 组件
      state.keepExclude = state.keepExclude.filter(item => item !== payload)
      state.keepExclude.push(payload)
    },
    [REMOVE_KEEP_EXCLUDE] (state, payload) {
      // TODO 匹配所有不要缓存的组件 不包含 payload
      state.keepExclude = state.keepExclude.filter(item => item !== payload)
    }
  }
})
  1. 定义两个方法,方便在组件中使用
import store from '@/store'

const newCache = function (routerName) {
  store.commit('ADD_KEEP_INCLUDE', routerName)
  store.commit('REMOVE_KEEP_EXCLUDE', routerName)
}
const clearCache = function (routerName) {
  store.commit('ADD_KEEP_EXCLUDE', routerName)
  store.commit('REMOVE_KEEP_INCLUDE', routerName)
}
export {
  clearCache, newCache
}
  1. 配合beforeRouteEnter和beforeRouteLeave实现数据缓存
import { clearCache, newCache } from '@/common/index'
export default {
  name: 'keepa',
  beforeRouteEnter (to, from, next) {
    newCache('keepa')
    next()
  },
  beforeRouteLeave (to, from, next) {
    if (to.name.indexOf('home') > -1) {
      clearCache('keepa')
    }
    next()
  }
}
  1. 在路由页面外增加keep-alive组件
<keep-alive :include="keepInclude" :exclude="keepExclude">
    <router-view :key="$route.fullPath"/>
</keep-alive>

重要注意事项

  1. include和exclude中的组件名称是单文件组件中的name的值,不是router的name
  2. 建议include和exclude使用数组,使用字符串的时候,include和exclude为空的时候,默认是缓存组件的。导致误缓存组件
  3. exclude的优先级比include高,如果某个组件在include和exclude中都存在,那么不会被缓存