feat(types): add emits and slots type to `FunctionalComponent` (#8644)

This commit is contained in:
三咲智子 Kevin Deng 2023-12-08 22:24:58 +08:00 committed by GitHub
parent bfb856565d
commit 927ab17cfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 17 deletions

View File

@ -8,14 +8,22 @@ import {
FunctionalComponent, FunctionalComponent,
ComponentPublicInstance, ComponentPublicInstance,
toRefs, toRefs,
SetupContext SetupContext,
EmitsOptions
} from 'vue' } from 'vue'
import { describe, expectAssignable, expectType, IsAny } from './utils' import { describe, expectAssignable, expectType, IsAny } from './utils'
declare function extractComponentOptions<Props, RawBindings>( declare function extractComponentOptions<
obj: Component<Props, RawBindings> Props,
RawBindings,
Emits extends EmitsOptions | Record<string, any[]>,
Slots extends Record<string, any>
>(
obj: Component<Props, RawBindings, any, any, any, Emits, Slots>
): { ): {
props: Props props: Props
emits: Emits
slots: Slots
rawBindings: RawBindings rawBindings: RawBindings
setup: ShallowUnwrapRef<RawBindings> setup: ShallowUnwrapRef<RawBindings>
} }
@ -455,11 +463,27 @@ describe('functional', () => {
}) })
describe('typed', () => { 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 { props } = extractComponentOptions(MyComponent) const MyComponent: FunctionalComponent<Props, Emits, Slots> = (
props,
{ emit, slots }
) => {
expectType<Props>(props)
expectType<{
(event: 'change', value: string): void
(event: 'inc', value: number): void
}>(emit)
expectType<Slots>(slots)
}
expectType<number>(props.foo) const { props, emits, slots } = extractComponentOptions(MyComponent)
expectType<Props>(props)
expectType<Emits>(emits)
expectType<Slots>(slots)
}) })
}) })
@ -481,4 +505,18 @@ describe('SetupContext', () => {
expectAssignable<SetupContext<{ b: () => true }>>(wider) expectAssignable<SetupContext<{ b: () => 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)
})
}) })

View File

@ -51,7 +51,8 @@ import {
EmitFn, EmitFn,
emit, emit,
normalizeEmitsOptions, normalizeEmitsOptions,
EmitsToProps EmitsToProps,
ShortEmitsToObject
} from './componentEmits' } from './componentEmits'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
@ -160,16 +161,17 @@ export interface ComponentInternalOptions {
export interface FunctionalComponent< export interface FunctionalComponent<
P = {}, P = {},
E extends EmitsOptions = {}, E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any S extends Record<string, any> = any,
EE extends EmitsOptions = ShortEmitsToObject<E>
> extends ComponentInternalOptions { > extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor // use of any here is intentional so it can be a valid JSX Element constructor
( (
props: P & EmitsToProps<E>, props: P & EmitsToProps<EE>,
ctx: Omit<SetupContext<E, IfAny<S, {}, SlotsType<S>>>, 'expose'> ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>
): any ): any
props?: ComponentPropsOptions<P> props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[] emits?: EE | (keyof EE)[]
slots?: IfAny<S, Slots, SlotsType<S>> slots?: IfAny<S, Slots, SlotsType<S>>
inheritAttrs?: boolean inheritAttrs?: boolean
displayName?: string displayName?: string
@ -192,10 +194,12 @@ export type ConcreteComponent<
RawBindings = any, RawBindings = any,
D = any, D = any,
C extends ComputedOptions = ComputedOptions, C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions M extends MethodOptions = MethodOptions,
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any
> = > =
| ComponentOptions<Props, RawBindings, D, C, M> | ComponentOptions<Props, RawBindings, D, C, M>
| FunctionalComponent<Props, any> | FunctionalComponent<Props, E, S>
/** /**
* A type used in public APIs where a component type is expected. * A type used in public APIs where a component type is expected.
@ -206,9 +210,11 @@ export type Component<
RawBindings = any, RawBindings = any,
D = any, D = any,
C extends ComputedOptions = ComputedOptions, C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions M extends MethodOptions = MethodOptions,
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any
> = > =
| ConcreteComponent<Props, RawBindings, D, C, M> | ConcreteComponent<Props, RawBindings, D, C, M, E, S>
| ComponentPublicInstanceConstructor<Props> | ComponentPublicInstanceConstructor<Props>
export type { ComponentOptions } export type { ComponentOptions }

View File

@ -55,6 +55,12 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[]
} }
: {} : {}
export type ShortEmitsToObject<E> = E extends Record<string, any[]>
? {
[K in keyof E]: (...args: E[K]) => any
}
: E
export type EmitFn< export type EmitFn<
Options = ObjectEmitsOptions, Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options Event extends keyof Options = keyof Options
@ -66,7 +72,9 @@ export type EmitFn<
{ {
[key in Event]: Options[key] extends (...args: infer Args) => any [key in Event]: Options[key] extends (...args: infer Args) => any
? (event: key, ...args: Args) => void ? (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] }[Event]
> >