vue2如何保持历史页面数据不变,实现返回后不重置数据(keep-alive)

我们在项目开发中遇到最多的就是跟列表相关的操作吧,有一点很蛋疼的就是用户从列表页点击条目进入详情页后再次返回列表页面,数据却自动刷新了,又回到了列表的头部!

拿电商项目来说吧,我正在某个类目下开心的翻着我要的商品,不知道翻了几页终于找到一款类似的商品,激动的赶紧点进去,却发现不是想要的,然后返回,怎么数据又重置了??还要让我再重新翻一遍么?我去年买了表。

这种情况在一些web项目中非常常见,特别是跟ajax相关的操作,但是却很少有开发者重视。这对用户体验真的很差。由于我用vue spa比较多,所以就聊下vue下该怎么来优化这块。

以下涉及到的知识点有:动态组件、生命周期、组件缓存(keep-alive)、vue-router

1. 为什么vue中会出现这种跳转后数据重置的情况

因为在不同组件中切换,vue都会重新创建一个组件实例,所以你之前的组件实例将不复存在,那数据就更不提了,而vue-router的底层就是在不同组件之间来回切换。

2. keep-alive来救场

keep-alive的作用就是把组件第一次创建的实例给缓存下来,再次切换回该组件时会去缓存里边找这个组件对应的实例,这样数据就会被保存下来,那么路由中用法就是在<router-view></router-view>的外层包裹上<keep-alive></keep-alive>

1
2
3
<keep-alive>
<router-view></router-view>
</keep-alive>

所有被keep-alive包裹的组件都将被缓存起来,这里是把路由的入口给包裹进去,这样经过该路由的组件都会包含进去。
下面我写一段示例代码来实际验证下这个keep-alive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
<div class="page">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
<div class="tab">
<router-link to="/">首页</router-link>
<router-link to="/list">列表页</router-link>
</div>
</div>

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 首页
const Index = {
template: `
<div>
<div>首页</div>
</div>
`,
}

// 列表页
const List = {
template: `
<div>
<div>列表页</div>
<div class="loading" v-if="loading">加载中...</div>
<router-link to="/detail" v-else><button>去详情页</button></router-link>
<button @click="nextPage">下一页(当前页 page={{ page }})</button>
</div>
`,
data() {
return {
loading: false,
page: 1
}
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
setTimeout(() => {
this.loading = false;
}, 2000);
},
nextPage() {
this.page ++;
}
}
}

// 详情页
const Detail = {
template: `
<div>
<div>详情页</div>
<button @click="back">返回列表页</button>
</div>
`,
methods: {
back() {
this.$router.go(-1);
}
}
}

// 定义路由
const routes = [
{ path: '/', component: Index },
{ path: '/list', component: List },
{ path: '/detail', component: Detail },
]

const router = new VueRouter({
routes
})

const app = new Vue({
router
}).$mount('#app')

可以通过上面的demo1发现,第一次进入列表页时候出现了“加载中”,以后返回或者重新进入列表页时候,“加载中”将不会出现,而且page值也没有改变,你可以试着去掉keep-alive再观察下。

3. 生命周期 activated

我们想要的效果基本上已经达到了,不过这里边其实还有一些问题,比如我们的列表页可以根据分类来筛选,而这个分类可以通过路由参数传递进来,也就是说列表页可以根据外部参数而刷新数据。但是我们现在并没有这样的效果,看这里

1
2
3
4
5
// list列表页
created() {
this.type = this.$route.query.type;
this.getList();
},

由于组件实例被缓存起来了,所以组件的created也将不会再次调用,那分类字段也将不会再次赋值。
不过vue提供了一个activated钩子在keep-alive 组件激活时调用。所以可以这样来实现。

1
2
3
4
5
6
7
8
9
10
// list列表页
watch: {
type() {
this.page = 1;
this.getList();
}
},
activated() {
this.type = this.$route.query.type;
},

这样可以达到根据外部分类参数的变化而加载不同分类数据。还能保证从其他页面返回后数据不丢失。

总结

我们的最终目的,无非是想保留之前页面的数据,但是由于vue的动态组件机制,不得不采取一些非正常手段来实现,keep-alive还有一些参数 include、exclude、max,具体用处就是来过滤要缓存的组件以及缓存组件的数量,可以用来优化性能。

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

注意,keep-alive的属性include、exclude、max中,设置的是组件name,不是vue-router中的路由name。