Skip to content

Commit 64aa81d

Browse files
committed
suspense
1 parent 3600bc0 commit 64aa81d

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

src/guide/built-ins/suspense.md

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,134 @@
1-
# Suspense <Badge type="warning" text="experimental" />
1+
---
2+
aside: deep
3+
---
24

3-
## Basic Usage
5+
# Suspense
6+
7+
:::warning Experimental Feature
8+
`<Suspense>` is an experimental feature. It is not guaranteed to reach stable status and the API may change before it does.
9+
:::
10+
11+
`<Suspense>` is a built-in component for orchestrating async dependencies in a component tree. It can render a loading state while waiting for multiple nested async dependencies down the component tree to be resolved.
12+
13+
## Async Dependencies
14+
15+
To explain the problem `<Suspense>` is trying to solve and how it interacts with these async dependencies, let's imagine a component hierarchy like the following:
16+
17+
```
18+
<Suspense>
19+
└─ <Dashboard>
20+
├─ <Profile>
21+
│ └─ <FriendStatus> (component with async setup())
22+
└─ <Content>
23+
├─ <ActivityFeed> (async component)
24+
└─ <Stats> (async component)
25+
```
26+
27+
In the component tree there are multiple nested components whose rendering depends on some async resource to be resolved first. Without `<Suspense>`, each of them will need to handle its own loading / error and loaded states. In the worst case scenario, we may see three loading spinners on the page, with content displayed at different times.
28+
29+
The `<Suspense>` component gives us the ability to display top-level loading / error states while we wait on these nested async dependencies to be resolved.
30+
31+
There are two types of async dependencies that `<Suspense>` can wait on:
32+
33+
1. Components with an async `setup()` hook. This includes components with `<script setup>` that contains top-level `await` expressions.
34+
35+
2. [Async Components](/guide/components/async.html).
436

537
### `async setup()`
638

39+
A Composition API component's `setup()` hook can be async:
40+
41+
```js
42+
export default {
43+
async setup() {
44+
const res = await fetch(...)
45+
const posts = await res.json()
46+
return {
47+
posts
48+
}
49+
}
50+
}
51+
```
52+
53+
If using `<script setup>`, the presence of top-level `await` expressions automatically makes the component an async dependency:
54+
55+
```vue
56+
<script setup>
57+
const res = await fetch(...)
58+
const posts = await res.json()
59+
</script>
60+
61+
<template>
62+
{{ posts }}
63+
</template>
64+
```
65+
766
### Async Components
867

9-
Async components are _suspensible_ by default. This means if it has a `<Suspense>` in the parent chain, it will be treated as an async dependency of that `<Suspense>`. In this case, the loading state will be controlled by the `<Suspense>`, and the component's own loading, error, delay and timeout options will be ignored.
68+
Async components are **"suspensible"** by default. This means that if it has a `<Suspense>` in the parent chain, it will be treated as an async dependency of that `<Suspense>`. In this case, the loading state will be controlled by the `<Suspense>`, and the component's own loading, error, delay and timeout options will be ignored.
1069

1170
The async component can opt-out of `Suspense` control and let the component always control its own loading state by specifying `suspensible: false` in its options.
1271

1372
## Loading State
1473

74+
The `<Suspense>` component has two slots: `#default` and `#fallback`. Both slots only allow for **one** immediate child node. The node in the default slot is shown if possible. If not, the node in the fallback slot will be shown instead.
75+
76+
```vue-html
77+
<Suspense>
78+
<!-- component with nested async dependencies -->
79+
<Dashboard />
80+
81+
<!-- loading state via #fallback slot -->
82+
<template #fallback>
83+
Loading...
84+
</template>
85+
</Suspense>
86+
```
87+
88+
On initial render, `<Suspense>` will render its default slot content in memory. If any async dependencies are encountered during the process, it will enter **pending** state. During pending state, the fallback content will be displayed. When all encountered async dependencies have been resolved, `<Suspense>` enters **resolved** state and the resolved default slot content is displayed.
89+
90+
If no async dependencies were encountered during the initial render, `<Suspense>` will directly go into resolved state.
91+
92+
Once in resolved state, `<Suspense>` will only revert to pending state if the root node of the `#default` slot is replaced. New async dependencies nested deeper in the tree will **not** cause the `<Suspense>` to revert into pending state.
93+
94+
When a revert happens, fallback content will not be immediately displayed. Instead, `<Suspense>` will display the previous `#default` content while waiting for the new content and its async dependencies to be resolved. This behavior can be configured with the `timeout` prop: `<Suspense>` will switch to fallback content if it takes longer than `timeout` to render the new default content. A `timeout` value of `0` will cause the fallback content to be displayed immediately when default content is replaced.
95+
96+
## Events
97+
98+
In addition to the `pending` event, the `<suspense>` component also has `resolve` and `fallback` events. The `resolve` event is emitted when new content has finished resolving in the `default` slot. The `fallback` event is fired when the contents of the `fallback` slot are shown.
99+
100+
The events could be used, for example, to show a loading indicator in front of the old DOM while new components are loading.
101+
15102
## Error Handling
103+
104+
`<Suspense>` currently does not provide error handling via the component itself - however, you can use the [`errorCaptured`](/api/options-lifecycle.html#errorcaptured) option or the [`onErrorCaptured()`](/api/composition-api-lifecycle.html#onerrorcaptured) hook to capture and handle async errors in the parent component of `<Suspense>`.
105+
106+
## Combining with Other Components
107+
108+
It is common to want to use `<Suspense>` in combination with the [`<Transition>`](./transition) and [`<KeepAlive>`](./keep-alive) components. The nesting order of these components is important to get them all working correctly.
109+
110+
In addition, these components are often used in conjunction with the `<RouterView>` component from [Vue Router](https://next.router.vuejs.org/).
111+
112+
The following example shows how to nest these components so that they all behave as expected. For simpler combinations you can remove the components that you don't need:
113+
114+
```vue-html
115+
<RouterView v-slot="{ Component }">
116+
<template v-if="Component">
117+
<Transition mode="out-in">
118+
<KeepAlive>
119+
<Suspense>
120+
<!-- main content -->
121+
<component :is="Component"></component>
122+
123+
<!-- loading state -->
124+
<template #fallback>
125+
Loading...
126+
</template>
127+
</Suspense>
128+
</KeepAlive>
129+
</Transition>
130+
</template>
131+
</RouterView>
132+
```
133+
134+
Vue Router has built-in support for [lazily loading components](https://next.router.vuejs.org/guide/advanced/lazy-loading.html) using dynamic imports. These are distinct from async components and currently they will not trigger `<Suspense>`. However, they can still have async components as descendants and those can trigger `<Suspense>` in the usual way.

src/guide/components/slots.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,24 @@ Here's the code passing content to all three slots to `<BaseLayout>` using the s
232232
</BaseLayout>
233233
```
234234

235+
When a component accepts both default slot and named slots, all top-level non-`<template>` nodes are implciitly treated as content for default slot. So the above can also be written as:
236+
237+
```vue-html
238+
<BaseLayout>
239+
<template #header>
240+
<h1>Here might be a page title</h1>
241+
</template>
242+
243+
<!-- implicit default slot -->
244+
<p>A paragraph for the main content.</p>
245+
<p>And another one.</p>
246+
247+
<template #footer>
248+
<p>Here's some contact info</p>
249+
</template>
250+
</BaseLayout>
251+
```
252+
235253
Now everything inside the `<template>` elements will be passed to the corresponding slots. The final rendered HTML will be:
236254

237255
```html

0 commit comments

Comments
 (0)