You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue Suspense仅直接访问异步组件时生效,路由跳转无加载提示

问题描述

项目包含Home和Tools两个路由:直接访问Tools路由时,Suspense的“Loading...”提示正常显示;但从Home路由跳转至Tools路由时,尽管异步组件需要加载2秒,加载提示却不显示。期望无论从哪个路由进入,加载异步组件时都能显示加载提示。

相关代码

App.vue

<script setup>
import Connexion from '@/components/Connexion.vue'
import { ref } from 'vue'

const drawer = ref(null);
</script>

<template>
  <v-app>
    <v-navigation-drawer
      expand-on-hover
      rail>

      <Suspense>
        <Connexion />
        <template #fallback>
          Chargement...
        </template>
      </Suspense>

      <v-divider></v-divider>

      <v-list density="compact" nav>
        <v-list-item prepend-icon="mdi-home" title="Home" value="home" @click="$router.push('/')"></v-list-item>
        <v-list-item prepend-icon="mdi-account-multiple" title="Tools" value="tools" @click="$router.push('/tools')"></v-list-item>
      </v-list>
    </v-navigation-drawer>

    <v-app-bar>
      <v-toolbar-title>My app</v-toolbar-title>
    </v-app-bar>

    <v-main fluid fill-width>
      XXX
      <v-container
        fluid
        >
      <router-view v-slot="{ Component }">
        <template v-if="Component">
          <Transition mode="out-in">
            <keep-alive>
              <Suspense>
                <component :is="Component" />
                <template #fallback>
                  Loading...
                </template>
              </Suspense>
            </keep-alive>
          </Transition>
        </template>
      </router-view>
      </v-container>
    </v-main>
  </v-app>
</template>

<style>
.v-enter-active,
.v-leave-active {
  transition: opacity 0.2s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

Tools.vue

<script>
import { defineComponent } from "vue";
export default defineComponent({
  async setup() {
    const apiUrl = import.meta.env.VITE_APP_API_URL;
    console.log("loading...")
    const demandes = await fetch(`${apiUrl}/tools`)
      .then((r) => r.json())
      .catch((e) => {
        console.log(e);
        return [];
      });
    await new Promise(r => setTimeout(r, 2000));
    console.log("LOADED!")

    return {
      demandes,
    }
  }
})
</script>
<template>
  <div>
    <h1>Tools</h1>
    <ul>
      <li
        v-for="tool in tools"
        :key="`tool-${tool.id}`"
        >
        {{ tool }}
      </li>
    </ul>
  </div>
</template>

路由配置

import { createRouter, createWebHistory } from 'vue-router';
import { nextTick } from 'vue';

const routes = [
{
  path: '/',
  name: 'Home',
  component: () => import('@/views/Home.vue'),
  meta: {
    title: 'Home',
  },
},
{
  path: '/tools',
  name: 'Tools',
  component: () => import('@/views/Tools.vue'),
  meta: {
    title: 'Tools',
  },
},
];

const router = createRouter({
  base: import.meta.env.VITE_APP_PUBLICPATH,
  history: createWebHistory(import.meta.env.VITE_APP_BASE_URL),
  routes,
});

const DEFAULT_TITLE = import.meta.env.VITE_APP_TITLE;
router.afterEach((to) => {
  nextTick(() => {
    document.title = to.meta.title ? `${DEFAULT_TITLE} - ${to.meta.title}` : DEFAULT_TITLE;
  });
});

export default router;

Home.vue

<script setup>
</script>

<template>
  <h1>Home</h1>
</template>

<style scoped>
</style>
解决方案

问题根源是<keep-alive>缓存了Tools组件,首次加载后,从Home跳转回Tools时,组件不会重新执行setup里的异步逻辑,导致Suspense无法触发fallback状态。以下是几种解决方式:

方法1:移除不必要的<keep-alive>

如果不需要缓存Tools组件,直接删除<keep-alive>标签:

<Transition mode="out-in">
  <Suspense>
    <component :is="Component" />
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</Transition>

方法2:控制<keep-alive>的缓存范围

给<keep-alive>添加include属性,只缓存Home组件(需给Home组件定义name属性):

<keep-alive include="Home">
  <Suspense>
    <component :is="Component" />
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</keep-alive>

方法3:组件激活时重新触发异步逻辑

保留<keep-alive>,修改Tools.vue,将异步逻辑封装成函数,在activated钩子中调用,并手动管理加载状态:

<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: 'Tools',
  setup() {
    const demandes = ref([]);
    const isLoading = ref(false);
    const loadData = async () => {
      isLoading.value = true;
      const apiUrl = import.meta.env.VITE_APP_API_URL;
      console.log("loading...")
      demandes.value = await fetch(`${apiUrl}/tools`)
        .then((r) => r.json())
        .catch((e) => {
          console.log(e);
          return [];
        });
      await new Promise(r => setTimeout(r, 2000));
      console.log("LOADED!")
      isLoading.value = false;
    }
    return {
      demandes,
      isLoading,
      loadData
    }
  },
  async activated() {
    await this.loadData();
  },
  async mounted() {
    await this.loadData();
  }
})
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  <div v-else>
    <h1>Tools</h1>
    <ul>
      <li
        v-for="tool in demandes"
        :key="`tool-${tool.id}`"
        >
        {{ tool }}
      </li>
    </ul>
  </div>
</template>

方法4:路由元信息控制缓存

在路由配置中给Tools添加meta: { keepAlive: false },然后在App.vue中动态判断是否缓存:

<Transition mode="out-in">
  <keep-alive v-if="$route.meta.keepAlive">
    <Suspense>
      <component :is="Component" />
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </keep-alive>
  <Suspense v-else>
    <component :is="Component" />
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</Transition>

路由配置修改:

{
  path: '/tools',
  name: 'Tools',
  component: () => import('@/views/Tools.vue'),
  meta: {
    title: 'Tools',
    keepAlive: false
  },
},

内容的提问来源于stack exchange,提问作者djcaesar9114

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 04:15:18