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

Functional and Async Component API Change #28

Closed
yyx990803 opened this issue Mar 21, 2019 · 5 comments
Closed

Functional and Async Component API Change #28

yyx990803 opened this issue Mar 21, 2019 · 5 comments

Comments

@yyx990803
Copy link
Member

yyx990803 commented Mar 21, 2019

  • Start Date: 2019-03-12
  • Target Major Version: 3.x
  • Reference Issues: N/A
  • Implementation PR: N/A

Summary

  • Functional components must be written as plain functions
    • { functional: true } option removed
    • <template functional> no longer supported
  • Async component must be created via the createAsyncComponent API method

Basic example

import { h } from 'vue'

const FunctionalComp = props => {
  return h('div', `Hello! ${props.name}`)
}
import { createAsyncComponent } from 'vue'

const AsyncComp = createAsyncComponent(() => import('./Foo.vue'))

Motivation

Simplify Functional Components

In 2.x, functional components must be created using the following format:

const FunctionalComp = {
  functional: true,
  render(h) {
    return h('div', `Hello! ${props.name}`)
  }
}

This has the following issues:

  • Even when the component needs nothing but the render function, it still needs to use the object with functional: true.

  • Some options are supported (e.g. props and inject) but others are not (e.g. components). However, users often expect all options to be supported because it looks so similar to a normal stateful component (especially when they use SFC with <template functional>).

Another aspect of the problem is that we've noticed some users are using functional components solely for performance reasons, e.g. in SFCs with <template functional>, and are requesting us to support more stateful component options in functional components. However, I don't think this is something we should invest more time in.

In v3, the performance difference between stateful and functional components has been drastically reduced and will be insignificant in most use cases. As a result there is no longer a strong incentive to use functional components just for performance, which also no longer justifies the maintenance cost of supporting <template functional>. Functional components in v3 should be used primarily for simplicity, not performance.

Detailed Design

In 3.x, we intend to support functional components only as plain functions:

import { h } from 'vue'

const FunctionalComp = (props, slots) => {
  return h('div', `Hello! ${props.name}`)
}
  • The functional option is removed, and object format with { functional: true } is no longer supported.

  • SFCs will no longer support <template functional> - if you need anything more than a function, just use a normal component.

  • The function signature has also changed - h is now imported globally. Instead of a render context, props and slots and other values are passed in. For more details on how the new arguments can replace 2.x functional render context, see the Render Function API Change RFC.

Runtime Props Validation

Props declaration is now optional (only necessary when runtime validation is needed). To add runtime validation or default values, attach props to the function itself:

const FunctionalComp = props => {
  return h('div', `Hello! ${props.name}`)
}

FunctionalComp.props = {
  name: String
}

Async Component Creation

With the functional component change, Vue's runtime won't be able to tell whether a function is being provided as a functional component or an async component factory. So in v3 async components must now be created via a new API method:

import { createAsyncComponent } from 'vue'

const AsyncComp = createAsyncComponent(() => import('./Foo.vue'))

The method also supports advanced options:

const AsyncComp = createAsyncComponent({
  factory: () => import('./Foo.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
})

This will make async component creation a little more verbose, but async component creation is typically a low-frequency use case, and are often grouped in the same file (the routing configuration).

Drawbacks

  • Migration cost

Alternatives

N/A

Adoption strategy

  • For functional components, a compatibility mode can be provided for one-at-a-time migration.

  • For async components, the migration is straightforward and we can emit warnings when function components return Promise instead of VNodes.

  • SFCs using <template functional> should be converted to normal SFCs.

@Akryum
Copy link
Member

Akryum commented Mar 21, 2019

  • Will functional component function be able to return a Promise?
  • attach props to the function itself => Will this play nice with TypeScript?

@posva
Copy link
Member

posva commented Mar 21, 2019

I think with TS, you should be able to use an interface to type both:

interface FunctionalProps {
  name: string
}

const FunctionalComp: FunctionalComponent<FunctionalProps> = props => {
  return h('div', `Hello! ${props.name}`)
}

FunctionalComp.props = {
  name: String
}

@octref
Copy link
Member

octref commented Mar 21, 2019

some users are using functional components solely for performance reasons, e.g. in SFCs with <template functional>

For me, the appeal had always been being able to write components with only template block, though.

<template functional>
  <div>
    {{ props.msg }}
  </div>
</template>

If this becomes unsupported, JSX becomes a superior way of writing functional component than SFC. NVM, just realized this would be valid in Vue 3 so all is good.

<template>
  <div>
    {{ $props.msg }}
  </div>
</template>

@yyx990803
Copy link
Member Author

@Akryum

Will functional component function be able to return a Promise?

No. It must return a VNode.

attach props to the function itself => Will this play nice with TypeScript?

Like @posva said, with TS you have to use the FunctionalComponent interface (if you want type for all arguments). Otherwise you can just type the argument yourself (still needs double declaration):

interface FunctionalProps {
  name: string
}

const FunctionalComp = (props: FunctionalProps) => {
  return h('div', `Hello! ${props.name}`)
}

FunctionalComp.props = {
  name: String
}

@yyx990803
Copy link
Member Author

Published: vuejs/rfcs#27

@github-actions github-actions bot locked and limited conversation to collaborators Nov 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants