Skip to content

Commit bb0a317

Browse files
authored
New Async Component API (#148)
1 parent bd9a5ed commit bb0a317

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
- Start Date: 2020-03-25
2+
- Target Major Version: 3.x
3+
- Reference Issues: https://github.com/vuejs/rfcs/pull/28
4+
5+
# Summary
6+
7+
Introduce a dedicated API for defining async components.
8+
9+
# Basic example
10+
11+
```js
12+
import { defineAsyncComponent } from "vue"
13+
14+
// simple usage
15+
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))
16+
17+
// with options
18+
const AsyncFooWithOptions = defineAsyncComponent({
19+
loader: () => import("./Foo.vue"),
20+
loadingComponent: LoadingComponent,
21+
errorComponent: ErrorComponent,
22+
delay: 200,
23+
timeout: 3000
24+
})
25+
```
26+
27+
# Motivation
28+
29+
Per changes introduced in [RFC-0008: Render Function API Change](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0008-render-function-api-change.md), in Vue 3 plain functions are now treated as functional components. Async components must now be explicitly defined via an API method.
30+
31+
# Detailed design
32+
33+
## Simple Usage
34+
35+
```js
36+
import { defineAsyncComponent } from "vue"
37+
38+
// simple usage
39+
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))
40+
```
41+
42+
`defineAsyncComponent` can accept a loader function that returns a Promise resolving to the actual component.
43+
44+
- If the resolved value is an ES module, the `default` export of the module will automatically be used as the component.
45+
46+
- **Difference from 2.x:** Note that the loader function no longer receives the `resolve` and `reject` arguments like in 2.x - a Promise must always be returned.
47+
48+
For code that relies on custom `resolve` and `reject` in the loader function, the conversion is straightforward:
49+
50+
```js
51+
// before
52+
const Foo = (resolve, reject) => {
53+
/* ... */
54+
}
55+
56+
// after
57+
const Foo = defineAsyncComponent(() => new Promise((resolve, reject) => {
58+
/* ... */
59+
}))
60+
```
61+
62+
## Options Usage
63+
64+
```js
65+
import { defineAsyncComponent } from "vue"
66+
67+
const AsyncFooWithOptions = defineAsyncComponent({
68+
loader: () => import("./Foo.vue"),
69+
loadingComponent: LoadingComponent,
70+
errorComponent: ErrorComponent,
71+
delay: 100, // default: 200
72+
timeout: 3000, // default: Infinity
73+
suspensible: false, // default: true
74+
onError(error, retry, fail, attempts) {
75+
if (error.message.match(/fetch/) && attempts <= 3) {
76+
retry()
77+
} else {
78+
fail()
79+
}
80+
}
81+
})
82+
```
83+
84+
- The `delay` and `timeout` options work exactly the same as 2.x.
85+
86+
**Difference from 2.x:**
87+
88+
- The `component` option is replaced by the new `loader` option, which accepts the same loader function as in the simple usage.
89+
90+
In 2.x, an async component with options is defined as
91+
92+
```ts
93+
() => ({
94+
component: Promise<Component>
95+
// ...other options
96+
})
97+
```
98+
99+
Whereas in 3.x it is now:
100+
101+
```ts
102+
defineAsyncComponent({
103+
loader: () => Promise<Component>
104+
// ...other options
105+
})
106+
```
107+
108+
- 2.x `loading` and `error` options are renamed to `loadingComponent` and `errorComponent` respectively to be more explicit.
109+
110+
## Retry Control
111+
112+
The new `onError` option provides a hook to perform customized retry behavior in case of a loader error:
113+
114+
``` js
115+
const Foo = defineAsyncComponent({
116+
// ...
117+
onError(error, retry, fail, attempts) {
118+
if (error.message.match(/fetch/) && attempts <= 3) {
119+
// retry on fetch errors, 3 max attempts
120+
retry()
121+
} else {
122+
fail()
123+
}
124+
}
125+
})
126+
```
127+
128+
Note that `retry/fail` are like `resolve/reject` of a promise: one of them must be called for the error handling to continue.
129+
130+
## Using with Suspense
131+
132+
Async component in 3.x 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.
133+
134+
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.
135+
136+
# Adoption strategy
137+
138+
- The syntax conversion is mechanical and can be performed via a codemod. The challenge is in determining which plain functions should be considered async components. Some basic heuristics can be used:
139+
140+
- Arrow functions that returns dynamic `import` call to `.vue` files
141+
- Arrow functions that returns an object with the `component` property being a dynamic `import` call.
142+
143+
Note this may not cover 100% of the existing usage.
144+
145+
- In the compat build, it is possible to check the return value of functional components and warn legacy async components usage. This should cover all Promise-based use cases.
146+
147+
- The only case that cannot be easily detected is 2.x async components using manual `resolve/reject` instead of returning promises. Manual upgrade will be required for such cases but they should be relatively rare.

0 commit comments

Comments
 (0)