效果展示
切换到plan1分支,直接看页面也可以。
包含的效果:缓存列表页数据、位置,并且在子页面操作列表页数据。
涉及技术点
- vue
- keep-alive
- vue-router
- router-view
- router-link
- router.beforeEach
一、缓存页面的时机
例如,用户从首页(shop)进入商品列表页(productList),然后进入商品详情页(detail)。这种场景下,什么时候把页面存起来?
市面上常见的方案:一进入商品列表页,立刻将页面缓存起来。本人觉得此方案非常不合理,这里不展开说明,用过的都懂。
本节课方案:
- 用户进入商品列表页,不做任何操作。
- 用户前往详情页,缓存商品列表页。
- 用户返回首页,删除商品列表页的缓存。
二、缓存页面
修改router文件
开发者希望用户从列表页进入详情页时能够缓存列表页。为此,可以在列表页的 meta 标签中添加一个名为 cacheCurrentPage 的属性,属性值为数组 ['detail'],以表明当前页面需要被缓存。参考下面代码
const routes = [
{
path: '/shop',
name: 'shop',
component: () => import('@/views/shop/shop.vue'),
meta: {
enterPageWithCache: ['productList', 'detail'],
}
},
{
path: '/shop/productList',
name: 'productList',
component: () => import('@/views/shop/productList.vue'),
meta: {
enterPageWithCache: ['detail'],
}
},
{
path: '/shop/productList/detail',
name: 'detail',
component: () => import('@/views/shop/detail.vue'),
},
];
修改app.vue文件
// 这里的代码记得更换
router.beforeEach((to, from: any) => {
if (from.meta.cacheCurrentPage) {
// 进入的页面在cacheCurrentPage中,缓存当前页面
if (from.meta.cacheCurrentPage.includes(to.name)) {
aliveComponent.value.push(from.name as string);
} else {
// 进入的页面不在cacheCurrentPage中,移除当前页面的缓存
aliveComponent.value = aliveComponent.value.filter(
(item) => item !== from.name,
);
}
}
// 去重
aliveComponent.value = [...new Set(aliveComponent.value)];
console.log('aliveComponent.value', aliveComponent.value);
});
通过以上的修改,页面已经被正确缓存起来。
注意这里的 include 中的name,不是router中的name,是组件中的name。
三、记录列表页滚动条位置
const router = createRouter({
history: createWebHistory(),
routes, // `routes: routes` 的缩写
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
},
});
上面的代码是官网给的案例,可以记录滚动条位置。
测试阶段发现的问题
经过测试发现vue2和vue3中keep-alive有一个比较大的区别,keep-alive的缓存机制不同。vue2中缓存DOM树和数据结构;vue3中只缓存数据结构。这样就会引发两个问题。
问题一:将上述方案放入vue3中发现,vue3中keep-alive只缓存了数据,没有缓存DOM树,如果用户在长页面滚动一段距离后进入了一个短页面,然后再返回到需要滚动到指定位置的长页面时,DOM节点尚未完全渲染出来,页面的高度不够,因此无法滚动到正确的位置。vue2中并不存在这个问题。
解决方案:给router-view加一个min-height即可。代码如下:
const router = createRouter({
history: createWebHistory(),
routes, // `routes: routes` 的缩写
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
// page是router-view的类名 vue2不需要此操作
document.querySelector('#page').style.minHeight =
savedPosition.top + window.screen.height + 'px';
return savedPosition;
} else {
return { top: 0 };
}
},
});
当然scrollBehavior中添加setTimeout也可以,但是这种交互体验不好。
问题二:vue2中刚进入列表页,立即跳转到详情页,列表页面并没有被缓存起来。 vue3中不存在这个问题
解决方案:把next放在nextTick中,代码如下:
router.beforeEach((to, from, next) => {
if (from.meta.cacheCurrentPage) {
// 进入的页面在cacheCurrentPage中,缓存当前页面
if (from.meta.cacheCurrentPage.includes(to.name)) {
this.aliveComponent.push(from.name);
} else {
// 进入的页面不在cacheCurrentPage中,移除当前页面的缓存
this.aliveComponent = this.aliveComponent.filter(
(item) => item !== from.name,
);
}
}
this.aliveComponent = [...new Set(this.aliveComponent)];
console.log('aliveComponent', this.aliveComponent)
this.$nextTick(() => {
next();
});
})
四、详情页删除列表页数据
这里直接使用EventBus,传递事件即可。
// main.js
import Vue from 'vue'
export const EventBus = new Vue();
// detail.js
deleteProduct() {
EventBus.$emit('deleteProduct', this.productName);
this.goBack();
}
// list.js
created() {
EventBus.$on("deleteProduct", (name) => {
// 这里的延时器是为了让用户看到删除动画
this.$nextTick(() => {
setTimeout(() => {
this.listData = this.listData.filter((item) => item.name !== name);
}, 500);
});
});
},
五、源码链接
master分支中的代码没有使用keep-alive功能。您可以在master分支按照上述方案逐步尝试并体验。 shopDemo: vue移动端长列表进详情页完整方案 - Gitee.com
plan2分支是最终完整的解决方案,将其分成两个分支方便大家对比文件变化。 shopDemo: vue移动端长列表进详情页完整方案 - Gitee.com