Vue中defineEmits类型实现原理及自定义函数接口类型匹配方法
Vue 2.7的组合式API中可使用defineEmits,用法如下:
const emit = defineEmit<{ (e: 'event-1', payload: number): void (e: 'event-2', payload: string): void }>()
调用时TypeScript能正确识别参数类型:
emit('event-1', 2) // 类型校验通过 emit('event-2', '2') // 类型校验通过
父组件添加监听器时,VS Code也能识别参数类型错误:
@event-1="(p: string) => console.log(p)" // 报错:p的类型应为number
但根据TypeScript文档,Parameters工具类型仅会返回最后一个重载签名的参数(比如上述示例中的event-2),我疑惑defineEmits是如何实现正确识别所有重载参数类型的。
现想请教:如何用**函数接口(而非映射类型)**为函数添加类型,使其能正确识别每个重载的参数类型?我编写了示例但未达到预期:
interface mySuperInterface { (e: 'event-1', payload: number): void (e: 'event-2', payload: string): void } function mySuperFunction<T extends (...args: any) => void>() { return function(...params: any[]) { console.log(params) } } const d = mySuperFunction<mySuperInterface>() d('event-1', 1) // 正确 d('event-2', 1) // 此处应触发类型错误,但实际未生效
我尝试过使用Parameters,但它依赖infer会取最后一个重载。我已知映射类型的实现方式,想了解defineEmits的核心实现思路。
实现思路解析
1. defineEmits 的核心逻辑
Vue的defineEmits并未依赖Parameters工具类型处理重载,而是利用了TypeScript的泛型约束与函数重载直接赋值兼容特性:
- 当你将重载函数接口作为泛型参数传入
defineEmits时,其返回值类型直接被约束为该接口类型,而非通过工具类型拆解后重新组装。 - 本质上,
defineEmits的类型定义是让返回的函数直接继承传入的重载接口的全部类型信息,而非提取单个重载的参数。
它的简化类型定义大致如下:
type EmitFn<T> = T extends (...args: any[]) => void ? T : never; function defineEmits<T extends (...args: any[]) => void>(): EmitFn<T> { // 运行时逻辑:返回触发事件的函数 return (...args: any[]) => { // 实际事件触发逻辑 } as EmitFn<T>; }
这里的关键是直接将泛型T作为返回值类型,而非用Parameters拆解T的参数,这样TypeScript会完整保留T的所有重载签名,不会仅取最后一个。
2. 修复自定义函数示例
你的mySuperFunction问题在于返回的函数未正确继承泛型参数T的类型,而是使用any[]作为参数类型。只需让返回函数直接适配泛型T的类型即可:
interface mySuperInterface { (e: 'event-1', payload: number): void (e: 'event-2', payload: string): void } // 让返回值直接继承泛型T的类型 function mySuperFunction<T extends (...args: any[]) => void>(): T { return function(...params: any[]) { console.log(params) } as unknown as T; } const d = mySuperFunction<mySuperInterface>() d('event-1', 1) // 正确 d('event-2', 1) // 触发类型错误:number类型不能赋值给string类型 d('event-3', 1) // 触发类型错误:不存在event-3重载签名
为何不用Parameters?
Parameters<T>处理重载函数时,确实只会返回最后一个重载的参数类型,这是TypeScript的设计限制——infer面对重载时仅能匹配最后一个签名。而直接让函数继承重载接口的类型,可绕过该限制,完整保留所有重载的类型校验。
内容的提问来源于stack exchange,提问作者Solid

