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>
仅作为依赖项解析和补丁的另一个边界。
相关