diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 312c583f..211ffb85 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -79,7 +79,7 @@ export interface ComponentInternalOptions { export interface FunctionalComponent< P = {}, - E extends EmitsOptions = Record + E extends EmitsOptions = {} > extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor (props: P, ctx: SetupContext): any diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 7bb20f73..69298112 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -25,13 +25,15 @@ export type EmitFn< Event extends keyof Options = keyof Options > = Options extends any[] ? (event: Options[0], ...args: any[]) => void - : UnionToIntersection< - { - [key in Event]: Options[key] extends ((...args: infer Args) => any) - ? (event: key, ...args: Args) => void - : (event: key, ...args: any[]) => void - }[Event] - > + : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function + ? (event: string, ...args: any[]) => void + : UnionToIntersection< + { + [key in Event]: Options[key] extends ((...args: infer Args) => any) + ? (event: key, ...args: Args) => void + : (event: key, ...args: any[]) => void + }[Event] + > export function emit( instance: ComponentInternalInstance, diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index 2b5d8c54..a00e465b 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -12,6 +12,7 @@ import { isObject, isArray } from '@vue/shared' import { RawSlots } from './componentSlots' import { FunctionalComponent, Component } from './component' import { ComponentOptions } from './componentOptions' +import { EmitsOptions } from './componentEmits' // `h` is a more user-friendly version of `createVNode` that allows omitting the // props when possible. It is intended for manually written render functions. @@ -107,8 +108,8 @@ export function h( ): VNode // functional component -export function h

( - type: FunctionalComponent

, +export function h( + type: FunctionalComponent, props?: (RawProps & P) | ({} extends P ? null : never), children?: RawChildren | RawSlots ): VNode diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index bc1e5f98..009ddbf5 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -6,7 +6,8 @@ import { reactive, createApp, expectError, - expectType + expectType, + ComponentPublicInstance } from './index' describe('with object props', () => { @@ -669,4 +670,17 @@ describe('emits', () => { expectError(this.$emit('nope')) } }) + + // without emits + defineComponent({ + setup(props, { emit }) { + emit('test', 1) + emit('test') + } + }) + + // emit should be valid when ComponentPublicInstance is used. + const instance = {} as ComponentPublicInstance + instance.$emit('test', 1) + instance.$emit('test') })