Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Async Component API #148

Merged
merged 5 commits into from
Apr 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions active-rfcs/0026-async-component-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
- Start Date: 2020-03-25
- Target Major Version: 3.x
- Reference Issues: https://github.com/vuejs/rfcs/pull/28

# Summary

Introduce a dedicated API for defining async components.

# Basic example

```js
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
})
```

# Motivation

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.

# Detailed design

## Simple Usage

```js
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` and `reject` arguments like in 2.x - a Promise must always be returned.

For code that relies on custom `resolve` and `reject` in the loader function, the conversion is straightforward:

```js
// before
const Foo = (resolve, reject) => {
/* ... */
}

// after
const Foo = defineAsyncComponent(() => new Promise((resolve, reject) => {
/* ... */
}))
```

## Options Usage

```js
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` and `timeout` options work exactly the same as 2.x.

**Difference from 2.x:**

- The `component` option is replaced by the new `loader` option, which accepts the same loader function as in the simple usage.

In 2.x, an async component with options is defined as

```ts
() => ({
component: Promise<Component>
// ...other options
})
```

Whereas in 3.x it is now:

```ts
defineAsyncComponent({
loader: () => Promise<Component>
// ...other options
})
```

- 2.x `loading` and `error` options are renamed to `loadingComponent` and `errorComponent` respectively to be more explicit.

## Retry Control

The new `onError` option provides a hook to perform customized retry behavior in case of a loader error:

``` js
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.

## Using with Suspense

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.

# Adoption strategy

- 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 dynamic `import` call.

Note this may not cover 100% of the existing usage.

- 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.