From 927ab17cfc645e82d061fdf227c34689491268e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 8 Dec 2023 22:24:58 +0800 Subject: [PATCH] feat(types): add emits and slots type to `FunctionalComponent` (#8644) --- packages/dts-test/component.test-d.ts | 50 ++++++++++++++++++--- packages/runtime-core/src/component.ts | 26 ++++++----- packages/runtime-core/src/componentEmits.ts | 10 ++++- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/packages/dts-test/component.test-d.ts b/packages/dts-test/component.test-d.ts index 5535419f198..98b3f42097b 100644 --- a/packages/dts-test/component.test-d.ts +++ b/packages/dts-test/component.test-d.ts @@ -8,14 +8,22 @@ import { FunctionalComponent, ComponentPublicInstance, toRefs, - SetupContext + SetupContext, + EmitsOptions } from 'vue' import { describe, expectAssignable, expectType, IsAny } from './utils' -declare function extractComponentOptions( - obj: Component +declare function extractComponentOptions< + Props, + RawBindings, + Emits extends EmitsOptions | Record, + Slots extends Record +>( + obj: Component ): { props: Props + emits: Emits + slots: Slots rawBindings: RawBindings setup: ShallowUnwrapRef } @@ -455,11 +463,27 @@ describe('functional', () => { }) describe('typed', () => { - const MyComponent: FunctionalComponent<{ foo: number }> = (_, _2) => {} + type Props = { foo: number } + type Emits = { change: [value: string]; inc: [value: number] } + type Slots = { default: (scope: { foo: string }) => any } + + const MyComponent: FunctionalComponent = ( + props, + { emit, slots } + ) => { + expectType(props) + expectType<{ + (event: 'change', value: string): void + (event: 'inc', value: number): void + }>(emit) + expectType(slots) + } - const { props } = extractComponentOptions(MyComponent) + const { props, emits, slots } = extractComponentOptions(MyComponent) - expectType(props.foo) + expectType(props) + expectType(emits) + expectType(slots) }) }) @@ -481,4 +505,18 @@ describe('SetupContext', () => { expectAssignable true }>>(wider) }) + + describe('short emits', () => { + const { + emit + }: SetupContext<{ + a: [val: string] + b: [val: number] + }> = {} as any + + expectType<{ + (event: 'a', val: string): void + (event: 'b', val: number): void + }>(emit) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 287e03d6c6d..b7bc8afbd6f 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -51,7 +51,8 @@ import { EmitFn, emit, normalizeEmitsOptions, - EmitsToProps + EmitsToProps, + ShortEmitsToObject } from './componentEmits' import { EMPTY_OBJ, @@ -160,16 +161,17 @@ export interface ComponentInternalOptions { export interface FunctionalComponent< P = {}, - E extends EmitsOptions = {}, - S extends Record = any + E extends EmitsOptions | Record = {}, + S extends Record = any, + EE extends EmitsOptions = ShortEmitsToObject > extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor ( - props: P & EmitsToProps, - ctx: Omit>>, 'expose'> + props: P & EmitsToProps, + ctx: Omit>>, 'expose'> ): any props?: ComponentPropsOptions

- emits?: E | (keyof E)[] + emits?: EE | (keyof EE)[] slots?: IfAny> inheritAttrs?: boolean displayName?: string @@ -192,10 +194,12 @@ export type ConcreteComponent< RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, - M extends MethodOptions = MethodOptions + M extends MethodOptions = MethodOptions, + E extends EmitsOptions | Record = {}, + S extends Record = any > = | ComponentOptions - | FunctionalComponent + | FunctionalComponent /** * A type used in public APIs where a component type is expected. @@ -206,9 +210,11 @@ export type Component< RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, - M extends MethodOptions = MethodOptions + M extends MethodOptions = MethodOptions, + E extends EmitsOptions | Record = {}, + S extends Record = any > = - | ConcreteComponent + | ConcreteComponent | ComponentPublicInstanceConstructor export type { ComponentOptions } diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index f3a30f7c953..893ebeaf3b7 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -55,6 +55,12 @@ export type EmitsToProps = T extends string[] } : {} +export type ShortEmitsToObject = E extends Record + ? { + [K in keyof E]: (...args: E[K]) => any + } + : E + export type EmitFn< Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options @@ -66,7 +72,9 @@ export type EmitFn< { [key in Event]: Options[key] extends (...args: infer Args) => any ? (event: key, ...args: Args) => void - : (event: key, ...args: any[]) => void + : Options[key] extends any[] + ? (event: key, ...args: Options[key]) => void + : (event: key, ...args: any[]) => void }[Event] >