vue移动端长列表进详情页完整方案

237 阅读3分钟

效果展示

切换到plan1分支,直接看页面也可以。

包含的效果:缓存列表页数据、位置,并且在子页面操作列表页数据。

涉及技术点

  • vue
  • keep-alive
  • vue-router
  • router-view
  • router-link
  • router.beforeEach

一、缓存页面的时机

例如,用户从首页(shop)进入商品列表页(productList),然后进入商品详情页(detail)。这种场景下,什么时候把页面存起来?

市面上常见的方案:一进入商品列表页,立刻将页面缓存起来。本人觉得此方案非常不合理,这里不展开说明,用过的都懂。

本节课方案:

  1. 用户进入商品列表页,不做任何操作。
  2. 用户前往详情页,缓存商品列表页。
  3. 用户返回首页,删除商品列表页的缓存。

二、缓存页面

修改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