Skip to content

Commit

Permalink
feat(types): add emits and slots type to FunctionalComponent (#8644)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Dec 8, 2023
1 parent bfb8565 commit 927ab17
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 17 deletions.
50 changes: 44 additions & 6 deletions packages/dts-test/component.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ import {
FunctionalComponent,
ComponentPublicInstance,
toRefs,
SetupContext
SetupContext,
EmitsOptions
} from 'vue'
import { describe, expectAssignable, expectType, IsAny } from './utils'

declare function extractComponentOptions<Props, RawBindings>(
obj: Component<Props, RawBindings>
declare function extractComponentOptions<
Props,
RawBindings,
Emits extends EmitsOptions | Record<string, any[]>,
Slots extends Record<string, any>
>(
obj: Component<Props, RawBindings, any, any, any, Emits, Slots>
): {
props: Props
emits: Emits
slots: Slots
rawBindings: RawBindings
setup: ShallowUnwrapRef<RawBindings>
}
Expand Down Expand Up @@ -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, Emits, Slots> = (
props,
{ emit, slots }
) => {
expectType<Props>(props)
expectType<{
(event: 'change', value: string): void
(event: 'inc', value: number): void
}>(emit)
expectType<Slots>(slots)
}

const { props } = extractComponentOptions(MyComponent)
const { props, emits, slots } = extractComponentOptions(MyComponent)

expectType<number>(props.foo)
expectType<Props>(props)
expectType<Emits>(emits)
expectType<Slots>(slots)
})
})

Expand All @@ -481,4 +505,18 @@ describe('SetupContext', () => {

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)
})
})
26 changes: 16 additions & 10 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ import {
EmitFn,
emit,
normalizeEmitsOptions,
EmitsToProps
EmitsToProps,
ShortEmitsToObject
} from './componentEmits'
import {
EMPTY_OBJ,
Expand Down Expand Up @@ -160,16 +161,17 @@ export interface ComponentInternalOptions {

export interface FunctionalComponent<
P = {},
E extends EmitsOptions = {},
S extends Record<string, any> = any
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any,
EE extends EmitsOptions = ShortEmitsToObject<E>
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(
props: P & EmitsToProps<E>,
ctx: Omit<SetupContext<E, IfAny<S, {}, SlotsType<S>>>, 'expose'>
props: P & EmitsToProps<EE>,
ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>
): any
props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[]
emits?: EE | (keyof EE)[]
slots?: IfAny<S, Slots, SlotsType<S>>
inheritAttrs?: boolean
displayName?: string
Expand All @@ -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<string, any[]> = {},
S extends Record<string, any> = any
> =
| 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.
Expand All @@ -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<string, any[]> = {},
S extends Record<string, any> = any
> =
| ConcreteComponent<Props, RawBindings, D, C, M>
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
| ComponentPublicInstanceConstructor<Props>

export type { ComponentOptions }
Expand Down
10 changes: 9 additions & 1 deletion packages/runtime-core/src/componentEmits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options
Expand All @@ -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]
>

Expand Down

0 comments on commit 927ab17

Please sign in to comment.