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

feat(types): defineComponent() with generics support #7963

Merged
merged 3 commits into from Mar 27, 2023

Conversation

yyx990803
Copy link
Member

@yyx990803 yyx990803 commented Mar 27, 2023

Breaking Changes

The type of defineComponent() when passing in a function has changed. This overload signature is rarely used in practice and the breakage will be minimal, so repurposing it to something more useful should be worth it.

Previously the return type was DefineComponent, now it is a function type that inherits the generics of the function passed in. The function passed in as the first argument now also requires a return type of a render function, as the signature is no longer meant to be used for other use cases.

Another side effect is that a very specific case of mixins usage breaks:

const a = {}
const b = {}
defineComponent({
  mixins: [defineComponent(a), defineComponent(b)],
  data() {}
})

Note the breakage only happens when calling defineComponent() inline in the mixins option on a separately declared plain object. This should be extremely unlikely in userland code because if one wants typed mixins, one will certainly define it like this to get type inference for the mixin itself:

const a = defineComponent({})
const b = defineComponent({})
defineComponent({
  mixins: [a, b],
  data() {}
})

This continues to work, so I believe this issue is negligible in practice. It would be nice to fix, but I couldn't figure it out.

Generics Example

const Comp = defineComponent(
    // TODO: babel plugin to auto infer runtime props options from type
    // similar to defineProps<{...}>()
    <T extends string | number>(props: { msg: T; list: T[] }) => {
      // use Composition API here like in <script setup>
      const count = ref(0)

      return () => (
        // return a render function (both JSX and h() works)
        <div>
          {props.msg} {count.value}
        </div>
      )
    }
  )

  expectType<JSX.Element>(<Comp msg="fse" list={['foo']} />)
  expectType<JSX.Element>(<Comp msg={123} list={[123]} />)

  expectType<JSX.Element>(
    // @ts-expect-error missing prop
    <Comp msg={123} />
  )

  expectType<JSX.Element>(
    // @ts-expect-error generics don't match
    <Comp msg="fse" list={[123]} />
  )
  expectType<JSX.Element>(
    // @ts-expect-error generics don't match
    <Comp msg={123} list={['123']} />
  )

Extra Options

This signature is expected to only be used with Composition API. Misc options can be passed via the second argument:

defineComponent(() => () => {}, {
  name: 'Foo',
  inheritAttrs: false
})

Only name, inheritAttrs, props and emits are allowed.

Runtime Props

The final component still need runtime props declared. This can be done automatically via a babel plugin, or manually like so:

defineComponent((props: { msg: string }) => () => {}, {
  props: ['msg']
  // OR
  props: { msg: String }
})

The type definition ensures that the manual type and the runtime options must match.

Emits Validation

Type inference based on runtime emits option is supported, including the matching onXXX prop on the resulting props interface.

const Foo = defineComponent(
  (props: { msg: string }, ctx) => {
    ctx.emit('foo')
    // @ts-expect-error
    ctx.emit('bar')
    return () => {}
  },
  {
    emits: ['foo']
  }
)
expectType<JSX.Element>(<Foo msg="hi" onFoo={() => {}} />)
// @ts-expect-error
expectType<JSX.Element>(<Foo msg="hi" onBar={() => {}} />)

BREAKING CHANGE: The type of `defineComponent()` when passing in
a function has changed. This overload signature was rarely used in the
past, so it is changed to something more useful.

  Previously the return type was `DefineComponent`, now it is a function
  type that inherits the generics of the function passed in. The
  function passed in as the first argument now also requires a return
  type of a render function, as the signature is no longer meant to be
  used for other use cases.
@yyx990803 yyx990803 merged commit d77557c into main Mar 27, 2023
13 checks passed
@yyx990803 yyx990803 deleted the defineComponent-generics branch March 27, 2023 10:28
IAmSSH pushed a commit to IAmSSH/core that referenced this pull request May 14, 2023
BREAKING CHANGE: The type of `defineComponent()` when passing in a function has changed. This overload signature is rarely used in practice and the breakage will be minimal, so repurposing it to something more useful should be worth it.

close vuejs#3102
@lockiechen
Copy link

is there a example about custom event generic support

@sxzz
Copy link
Member

sxzz commented Oct 14, 2023

Currently we cannot use generic + typed emits or slots (in other words only props can) due to limitation of TS

@higuaifan
Copy link

Currently we cannot use generic + typed emits or slots (in other words only props can) due to limitation of TS

I'm wondering why this issue was not mentioned in the documentation. Or rather, should this situation be included in the document to prevent others from having to spend time like I did to confirm the problem and search for issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants