Vue 3 中嵌套路由的悬念——为什么内容消失了?

问题描述 投票:0回答:2

我正在使用 Suspense,并在页面上获取异步数据。在初始后备加载指示器上可见,但在切换页面时我会保留当前页面内容,直到新页面准备就绪 +

nprogress
栏,因此网站的行为更像 SSR。还有Transition between,不过对于那个问题来说并不重要。

而且效果很好。问题是当我的页面有子页面(嵌套路由)时,也有 Suspense。当我在

/demo/demo1
这样的嵌套页面上并导航到主页时,
/demo
模板的内容保持原样,但子页面
demo1
的内容消失了。我需要保留嵌套的 Suspense,直到父 Suspense 准备就绪。有什么想法吗?

我尽可能地简化了代码。我在根应用程序和页面上使用相同的

<RouterView>
代码。

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Suspense>
      <component :is="Component" />
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </template>
</RouterView>

这里是现场复现:https://stackblitz.com/edit/vitejs-vite-gg6kqo?file=src/pages/demo/demo1.vue(没有nprogress,页面之间等待1s)

复制步骤:

  1. 点击“关于”-“主页”停留直到“关于”准备就绪,然后“关于”出现-工作正常。
  2. 点击“Demo”——出现“demo”根页面,出现嵌套“demo/demo1”路由的回退——工作正常。
  3. 点击“主页”-“demo/demo1”的内容立即消失。
javascript vue.js vuejs3 vue-router vue-suspense
2个回答
0
投票

我不会说这是最好的答案,但在我看来,为了使

<Suspense>
工作,它的孩子必须是一个异步组件。在这种情况下,
<RouterView>
被呈现为非异步组件(即使它的子组件是异步组件),因此根
<Suspense>
不会等待
<Suspense>
中的
<RouterView>

作为一种解决方法,我认为我们可以缓存组件,这样当路由器改变时它就不会看起来好像已经消失了(第三种情况)。

我的缓存方法是在

<script setup>
文件中添加这个
demo.vue

<script setup>
import { ref } from 'vue';

const CachedComponent = ref(null);

function cacheComponent(Component) {
  CachedComponent.value = Component || CachedComponent.value;
}
</script>

然后将同一个文件里面的

<RouterView>
修改成这样:

    ...
    <RouterView v-slot="{ Component }">
      {{ cacheComponent(Component) }}
      <template v-if="Component || CachedComponent">
        <Suspense>
          <component :is="Component || CachedComponent" />
          <template #fallback>
            <div style="font-size: 1.5rem">Loading Demo Nested...</div>
          </template>
        </Suspense>
      </template>
    </RouterView>
    ...

{{ cacheComponent(Component) }}
行将在渲染时缓存组件(之所以这样使用,是因为
<RouterView>
在安装插槽时没有回调)。
CachedComponent.value = Component || CachedComponent.value;
行将更新 CachedComponent 值,如果有 a 新的 Component 不是 falsy。如果它是假的(意味着它尚未安装或不再安装),它将呈现组件的先前最新缓存版本。我知道这很 hacky,但在我看来,这是一个非常简单的解决方法。

如果您有兴趣,这是分叉的 stackblitz


0
投票

当您有嵌套路线时,此时对于复杂项目根本不起作用,正如您所注意到的,它可以很好地处理单个级别的路线。

我为此奋斗了一段时间,尝试了很多组合,发现嵌套悬念被打破了,这是与被动用于路由器组件的

key
周围的功能崩溃有关,我尝试手动设置它产生了各种结果和其他无法解决/烦人的问题,包括与
keep-alive
.

的任何使用

无法使用

async setup
真的很烦人和不幸,尤其是在尝试 API 调用之前在协商其他承诺时暂停,我特别喜欢代码的干净程度。

我不得不接受异步

watch
,我设置了一个
loading = ref(true)
状态传递给自定义加载状态组件包装器,它将 I/O 块内容并在内容主体中显示加载进度。

// myPage.vue

<script setup lang="ts">

const props = defineProps<{
    id: string
}>()

const loading = ref(true)

watch(props, async () => {

  loading.value = true

  const authed = await auth.waitUntilAuthed() // true or false + redirect
    
  if(authed) {
    await myApi.endpoint.get(props)
    loading.value = false
  }
  

}, {immediate: true})

</script>

<template>
  <content-loader :loading="loading">
    <div>
      My page content
    </div>
  </content-loader>
</template>
// ContentLoader.vue

<script setup lang="ts">

withDefaults(defineProps<{
    loading: boolean
}>(), {
    loading: true
})

</script>

<template>

    <div class="d-flex fill-height justify-center align-center">

        <template v-if="$props.loading">

            <span>Content loading...

        </template>

        <template v-else>
            <slot/>
        </template>

    </div>

</template>

如果你愿意,你也可以在那里进行过渡。

我也在考虑通过将其移动到托管子

router-view
的父组件并在子页面中使用
emit
来切换加载来重新推断加载包装器和加载引用的重复。我还没试过。

© www.soinside.com 2019 - 2024. All rights reserved.