likes
comments
collection
share

KeepAlive在路由中的应用——保存页面状态

作者站长头像
站长
· 阅读数 22

最近业务开发过程中遇到一个很有意思的场景:列表页跳详情页,再折返回列表页时,需要还原离开时的状态。通常的做法是:离开时存储状态 --> 返回时还原状态。但实际上大部分情况下,我们完全可以省略这个步骤。我们只要缓存需要保存状态的页面,使其在离开时不被销毁,并在返回重新激活即可。

这其实是KeepAlive组件的一个典型使用场景,只不过此时KeepAlive的目标变成了整个页面,而非页面的一角,但本质是一样的。

业务场景

假设我们有这样一个场景:在列表页输入查询条件远程搜索到符合条件的n条数据,然后点进某一项的详情页面,查看完信息之后,我们要返回列表页,查看其他项目的详情页。

在没有做任何处理的情况下,页面将会重新加载,之前的搜索条件将被清空,搜索到的数据也会丢失。想要回到之前的状态,用户只能重新搜索,然后继续之前的操作。这听起来就很麻烦,特别是当网络不好的时候😔。

上面简单描述了业务场景,如果你还是不太清楚我在说什么的话,可以参考下面这张GIF:

  • 首先我们在manage页面进行筛选。
  • 随后点击选项查看详情,跳转到detail页面。从DevTools中可以看到,ListManage组件并未卸载,而是进入了inactive状态。
  • 然后折返manage页面。ListManage组件的状态由inactiveactive

可以看到,此时的manage页面与离开时一模一样,这就是我们想要实现的效果。

方案对比

常见方案主要有:跳转新页面、手动保存状态、KeepAlive,三种方案各有优劣。

  1. 跳转新页面:最简单的实现方式,打开一个新的浏览器Tab页即可保留原始页面。由于要新开页面,使用体验略显割裂。
  2. 手动保存状态:此种方式通常需要依赖全局存储,复杂程度和业务逻辑挂钩,页面越复杂,需要控制的细节越多,该方式也就越复杂。以过去的编码经验来讲,业务组件依赖全局状态,很容易滋生BUG,尤其是在多人合作的情况下,这需要做一些取舍了。
  3. KeepAlive: 复杂程度适中,但有一定的门槛。需要对VueRouter的路由配置方式有一定的理解,否则很可能弄巧成拙。但这种方式确是使用体验最好的一种。

KeepAlive具体实现

前两种实现方式没什么多说的,就是字面意思是,本文主要探讨KeepAlive的实现方式。

首先我们需要一个辅助组件,我们暂时命名为RouterAlive。这是一个非常简单的基于KeepAlive和RouterView封装的路由根组件。

import { defineComponent, KeepAlive } from 'vue';
import { RouterView } from 'vue-router';

export const RouterAlive = defineComponent({
  name: 'RouterAlive',
  setup(props, ctx) {
    return () => (
      <RouterView
        v-slots={{
          default: ({ Component }) => (
            <KeepAlive {...ctx.attrs}>
              <Component />
            </KeepAlive>
          ),
        }}
      />
    );
  },
});

然后我们在路由配置中引用该组件,并设置我们需要缓存的组件,此处我们要缓存列表页组件ListManage,配置如下:

export default [
  {
    path: '/list',
    redirect: '/list/manage',
    component: RouterAlive,           // ① 我们封装的组件
    props: { include: 'ListManage' }, // ② 设定我们想要缓存的组件
    children: [
      {
        path: 'manage',
        meta: { name: '列表页' },
        component: ListManage,        // ③ 需要保证该组件的名称能够被KeepAlive匹配
      },
      {
        path: 'detail/:listId(\\d+)',
        meta: { name: '详情页' },
        props: route => ({ listId: route.params.listId }),
        component: ItemDetail,
      },
      {
        path: 'edit/:listId(\\d+)',
        meta: { name: '编辑页' },
        props: route => ({ listId: route.params.listId }),
        component: EditItem,
      },
    ]
  }
]

上面的配置达成的效果是:在RouterAlive组件下配置的children路由中随意跳转时,设定缓存的组件将会自动缓存和激活,而不会卸载。需要注意的是,如果目标路由脱离了children路由的范畴,那缓存的组件还是会被卸载

通常我们会把相关的一组路由放在一起,配合RouterAlive可以极大地提升页面跳转性能和用户体验。具体怎么分组可以根据业务场景灵活配置。但如果你几乎从未使用过children路由,而是将所有路由拍平成一个一维数组,那么你可能需要重新审视一下你的路由配置方式了,可以参考另一篇文章——你可能没用过的,VueRouter高级匹配模式

经过上面的开发,现在假设我们从列表页出发,跳转到详情页也好,编辑页也好,当用户完成一堆操作并返回最初的列表页时,只要没有刷新页面,都可以无缝衔接之前的上下文,继续工作。这在一定程度上提升了用户体验和平台使用效率。

需要注意的是,KeepAlive组件依赖组件名称进行匹配,如果被缓存的组件没有提供name选项,则无法正确被缓存,常见于使用了script setup语法糖的vue组件,这类组件暂时无法提供name选项,因此无法被KeepAlive缓存。由于被缓存的组件将会多出onActivited/onDeactivited两个生命周期函数,原组件生命周期可能需要根据情况调整。

总结

通过VueRouterRouterAlive的巧妙结合,我们可以方便地实现页面缓存的需求,且同时兼顾用户体验和页面性能。正所谓站在巨人的肩膀上,相比通过手动编写页面状态控制逻辑而实现的“伪缓存”效果,该方案具备诸多优势,例如,代码具备通用性、且对原页面的代码注入较少等。

如有错误,欢迎斧正。

转载自:https://juejin.cn/post/7139090134695149604
评论
请登录