|
1 |
| -# Suspense <Badge type="warning" text="experimental" /> |
| 1 | +--- |
| 2 | +aside: deep |
| 3 | +--- |
2 | 4 |
|
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). |
4 | 36 |
|
5 | 37 | ### `async setup()`
|
6 | 38 |
|
| 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 | + |
7 | 66 | ### Async Components
|
8 | 67 |
|
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. |
10 | 69 |
|
11 | 70 | 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.
|
12 | 71 |
|
13 | 72 | ## Loading State
|
14 | 73 |
|
| 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 | + |
15 | 102 | ## 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. |
0 commit comments