- Start Date: 2020-03-25
- Target Major Version: 3.x
- Reference Issues: #28
Introduce a dedicated API for defining async components.
import { defineAsyncComponent } from "vue"
// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))
// with options
const AsyncFooWithOptions = defineAsyncComponent({
loader: () => import("./Foo.vue"),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
Per changes introduced in RFC-0008: Render Function API Change, in Vue 3 plain functions are now treated as functional components. Async components must now be explicitly defined via an API method.
import { defineAsyncComponent } from "vue"
// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"))
defineAsyncComponent
can accept a loader function that returns a Promise resolving to the actual component.
-
If the resolved value is an ES module, the
default
export of the module will automatically be used as the component. -
Difference from 2.x: Note that the loader function no longer receives the
resolve
andreject
arguments like in 2.x - a Promise must always be returned.For code that relies on custom
resolve
andreject
in the loader function, the conversion is straightforward:// before const Foo = (resolve, reject) => { /* ... */ } // after const Foo = defineAsyncComponent(() => new Promise((resolve, reject) => { /* ... */ }))
import { defineAsyncComponent } from "vue"
const AsyncFooWithOptions = defineAsyncComponent({
loader: () => import("./Foo.vue"),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 100, // default: 200
timeout: 3000, // default: Infinity
suspensible: false, // default: true
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
retry()
} else {
fail()
}
}
})
- The
delay
andtimeout
options work exactly the same as 2.x.
Difference from 2.x:
-
The
component
option is replaced by the newloader
option, which accepts the same loader function as in the simple usage.In 2.x, an async component with options is defined as
() => ({ component: Promise<Component> // ...other options })
Whereas in 3.x it is now:
defineAsyncComponent({ loader: () => Promise<Component> // ...other options })
-
2.x
loading
anderror
options are renamed toloadingComponent
anderrorComponent
respectively to be more explicit.
The new onError
option provides a hook to perform customized retry behavior in case of a loader error:
const Foo = defineAsyncComponent({
// ...
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// retry on fetch errors, 3 max attempts
retry()
} else {
fail()
}
}
})
Note that retry/fail
are like resolve/reject
of a promise: one of them must be called for the error handling to continue.
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.
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.
-
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:
- Arrow functions that returns dynamic
import
call to.vue
files - Arrow functions that returns an object with the
component
property being a dynamicimport
call.
Note this may not cover 100% of the existing usage.
- Arrow functions that returns dynamic
-
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.
-
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.