Suspense
实验性功能
<Suspense>是一个实验性功能。它可能无法达到稳定状态,并且在其达到稳定状态之前,API可能会发生变化。
<Suspense>是一个内置组件,用于在组件树中协调异步依赖。在等待多个嵌套异步依赖在组件树中解决时,它可以渲染加载状态。
异步依赖
为了解释<Suspense>试图解决的问题以及它与这些异步依赖的交互方式,让我们想象一个如下所示的组件层次结构
<Suspense>
└─ <Dashboard>
├─ <Profile>
│ └─ <FriendStatus> (component with async setup())
└─ <Content>
├─ <ActivityFeed> (async component)
└─ <Stats> (async component)在组件树中,有多个嵌套组件,它们的渲染依赖于一些需要首先解决的异步资源。在没有<Suspense>的情况下,每个组件都需要处理自己的加载/错误和加载完成状态。在最坏的情况下,我们可能在页面上看到三个加载指示器,内容在不同时间显示。
<Suspense>组件为我们提供了在等待这些嵌套异步依赖解决时显示顶层加载/错误状态的能力。
<Suspense>可以等待两种类型的异步依赖
具有异步
setup()钩子的组件。这包括使用带有顶级await表达式的<script setup>的组件。异步组件.
async setup()
Composition API 组件的 setup() 钩子可以是异步的
js
export default {
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}如果使用 <script setup>,顶级 await 表达式的存在会自动使组件成为异步依赖
vue
<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>
<template>
{{ posts }}
</template>异步组件
异步组件默认是 "可挂起的"。这意味着如果它在父级链中有一个 <Suspense>,它将被视为该 <Suspense> 的异步依赖。在这种情况下,加载状态将由 <Suspense> 控制,并且将忽略组件自己的加载、错误、延迟和超时选项。
异步组件可以通过在其选项中指定 suspensible: false 来选择退出 Suspense 控制,并让组件始终控制其自己的加载状态。
加载状态
<Suspense> 组件有两个插槽:#default 和 #fallback。这两个插槽都只允许有一个直接子节点。如果可能,将显示默认插槽中的节点。如果不可以,将显示回退插槽中的节点。
template
<Suspense>
<!-- component with nested async dependencies -->
<Dashboard />
<!-- loading state via #fallback slot -->
<template #fallback>
Loading...
</template>
</Suspense>在初次渲染时,<Suspense> 将在内存中渲染其默认插槽内容。如果在处理过程中遇到任何异步依赖,它将进入一个 挂起 状态。在挂起状态下,将显示回退内容。当所有遇到的异步依赖都已解决,<Suspense> 进入一个 已解决 状态,并显示已解决的默认插槽内容。
如果在初次渲染过程中没有遇到任何异步依赖,<Suspense> 将直接进入已解决状态。
一旦进入已解决状态,<Suspense> 只有在 #default 插槽的根节点被替换时才会回到挂起状态。嵌套在树中的新异步依赖 不会 导致 <Suspense> 回到挂起状态。
当发生回退时,回退内容不会立即显示。相反,<Suspense> 将在等待新内容及其异步依赖解决的同时显示之前的 #default 内容。此行为可以通过 timeout 属性进行配置:<Suspense> 将在 timeout 指定的时间后切换到回退内容。如果 timeout 的值为 0,则当默认内容被替换时,将立即显示回退内容。
事件
<Suspense> 组件发出 3 个事件:pending、resolve 和 fallback。当进入挂起状态时,将触发 pending 事件。当新内容在 default 插槽中完成解决时,将发出 resolve 事件。当显示 fallback 插槽的内容时,将触发 fallback 事件。
可以使用这些事件,例如,在加载新组件时在旧 DOM 前显示加载指示器。
错误处理
<Suspense> 目前不通过组件本身提供错误处理 - 然而,您可以使用 errorCaptured 选项或 onErrorCaptured() 钩子来捕获和处理 <Suspense> 父组件中的异步错误。
与其他组件结合
在组合使用<Transition>和<KeepAlive>组件时,通常需要使用<Suspense>。这些组件的嵌套顺序对于它们正常工作至关重要。
此外,这些组件通常与Vue Router中的<RouterView>组件一起使用。
以下示例展示了如何嵌套这些组件,以便它们都按预期工作。对于更简单的组合,您可以删除不需要的组件。
template
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Transition mode="out-in">
<KeepAlive>
<Suspense>
<!-- main content -->
<component :is="Component"></component>
<!-- loading state -->
<template #fallback>
Loading...
</template>
</Suspense>
</KeepAlive>
</Transition>
</template>
</RouterView>Vue Router内置了对使用动态导入进行懒加载组件的支持。这些与异步组件不同,目前它们不会触发<Suspense>。然而,它们可以作为异步组件的子组件,并且可以通过常规方式触发<Suspense>。
嵌套Suspense
- 仅在3.3及以上版本支持
当我们有多个异步组件(常见于嵌套或基于布局的路由)时,如下所示:
template
<Suspense>
<component :is="DynamicAsyncOuter">
<component :is="DynamicAsyncInner" />
</component>
</Suspense><Suspense>创建了一个边界,将解析树中的所有异步组件,正如预期的那样。然而,当我们更改DynamicAsyncOuter时,<Suspense>正确等待它,但当更改DynamicAsyncInner时,嵌套的DynamicAsyncInner将渲染一个空节点,直到它被解析(而不是之前的节点或回退插槽)。
为了解决这个问题,我们可以使用嵌套的<Suspense>来处理嵌套组件的补丁,如下所示:
template
<Suspense>
<component :is="DynamicAsyncOuter">
<Suspense suspensible> <!-- this -->
<component :is="DynamicAsyncInner" />
</Suspense>
</component>
</Suspense>如果您没有设置suspensible属性,父级<Suspense>会将内部<Suspense>视为同步组件。这意味着它有自己的回退插槽,如果两个Dynamic组件同时更改,在子<Suspense>加载其自己的依赖项树时,可能会有空节点和多个补丁周期,这可能不是所希望的。当设置了该属性后,所有异步依赖项处理都由父级<Suspense>处理(包括发出的事件),而内部<Suspense>仅作为依赖项解析和补丁的另一个边界。
相关