]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(types): add emits and slots type to `FunctionalComponent` (#8644)
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Fri, 8 Dec 2023 14:24:58 +0000 (22:24 +0800)
committerGitHub <noreply@github.com>
Fri, 8 Dec 2023 14:24:58 +0000 (22:24 +0800)
packages/dts-test/component.test-d.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentEmits.ts

index 5535419f1988ab3766847a953c36f7c9a196e14b..98b3f42097b518cf70d7f0f3001344be1855726b 100644 (file)
@@ -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>
 }
@@ -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)
   })
 })
 
@@ -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)
+  })
 })
index 287e03d6c6d0171e8b930b4d7d707d734e72f47b..b7bc8afbd6f3dc271fcd9f5f9c5393b7660027d6 100644 (file)
@@ -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<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
@@ -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.
@@ -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 }
index f3a30f7c953bd137e1484adb7737c17d6204e212..893ebeaf3b7a58ec8a18535c2f2365521bf5a039 100644 (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<
   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]
       >