多标签页多用在中后台管理系统,能够让用户同时打开多个标签页,而且不会丢失之前填写的内容,操作起来会比较方便。虽然部分开源项目有多标签页的功能,但就体验来看,算不上特别好。

目标#

  1. 可以通过router.push实现打开标签页
  2. 同一路由组件可以多开并且数据能够缓存下来
  3. 不需要处理是否缓存导致的生命周期不一致的问题
  4. 多标签页可以关闭,同时KeepAlive中的缓存清除

存在的问题#

要实现多标签页的缓存,最简单的方法就是用RouterView配合KeepAlive。

<RouterView v-slot="{ Component }">
  <KeepAlive>
    <component :is="Component" />
  </KeepAlive>
</RouterView>

然而,这个方案存在几个问题:

  1. 不能重复打开同一个路由,而是原有的组件被激活
  2. 组件生命周期发生变化

不能重复打开路由#

如果给路由添加参数,打开第一次没有任何问题,但如果换另一个参数打开,还会是之前的页面,因为组件被缓存下来了。

例如:

新增一个路由 counter,在页面上添加RouterLink,并使用不同的参数

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/home">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/counter?id=1">Counter 1</RouterLink>
        <RouterLink to="/counter?id=2">Counter 2</RouterLink>
      </nav>
    </div>
  </header>
<RouterView v-slot="{ Component }">
  <KeepAlive>
    <component :is="Component" />
  </KeepAlive>
</RouterView>
</template>

然后再Counter组件中获取id参数,分别点击Counter 1和Counter 2,会发现点击Counter 1时获取到的id是1,点击Counter 2时却没有任何变化,而且两个RouterLink同时是激活状态。

image

组件生命周期变化#

和上一个问题有所关联,因为组件没有重新加载,在需要重新获取数据时,KeepAlive改变了组件的生命周期,添加了onActivatedonDeactivated生命周期。

添加一个组件测试生命周期:

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>

<script setup>

import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount, onActivated, onDeactivated } from 'vue'

onMounted(() => { console.log("onMounted") })
onUpdated(() => { console.log("onUpdated") })
onUnmounted(() => { console.log("onUnmounted") })
onBeforeMount(() => { console.log("onBeforeMount") })
onBeforeUpdate(() => { console.log("onBeforeUpdate") })
onBeforeUnmount(() => { console.log("onBeforeUnmount") })
onActivated(() => { console.log("onActivated") })
onDeactivated(() => { console.log("onDeactivated") })
</script>

<style>
@media (min-width: 1024px) {
  .about {
    min-height: 100vh;
    display: flex;
    align-items: center;
  }
}
</style>

再修改App.vue

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />

    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/home">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/counter?id=1">Counter 1</RouterLink>
        <RouterLink to="/counter?id=2">Counter 2</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView v-slot="{ Component }">
    <!-- <KeepAlive> -->
      <component :is="Component" />
    <!-- </KeepAlive> -->
  </RouterView>
</template>

<script setup>
import { watch } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'

const route = useRoute()

watch(route, () => {
  console.log("页面切换", route.fullPath)
})

</script>

先从Home切换到About再切换回Home再切换回About。

查看在不使用KeepAlive切换页面时候的输出,onBeforeMount -> onMounted -> onBeforeUnmount -> onUnMounted 循环

image

使用KeepAlive的情况,情况就复杂很多,每次切换到页面时会激活onActivated钩子,正常情况下可以通过onActivated钩子获取路由参数,重新获取数据。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐