我正在使用 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)
复制步骤:
我不会说这是最好的答案,但在我看来,为了使
<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,但在我看来,这是一个非常简单的解决方法。
当您有嵌套路线时,此时对于复杂项目根本不起作用,正如您所注意到的,它可以很好地处理单个级别的路线。
我为此奋斗了一段时间,尝试了很多组合,发现嵌套悬念被打破了,这是与被动用于路由器组件的
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
来切换加载来重新推断加载包装器和加载引用的重复。我还没试过。