如何让TypeScript在保留显式类型时推断泛型方法参数类型
TypeScript 类型校验问题解决方案
问题复现
以下代码会触发类型不匹配报错:
const variant1 = (data: { hello: number }) => {/* Do some work here... */}; const variant2 = (data: { hi: string }) => {/* Do some work here... */}; interface Data<T = unknown> { callback: (data: T) => void; } const f: Record<string, Data> = { name1: { callback: variant1 }, name2: { callback: variant2 }, // 报错:'(data: {hello: number}) => void' is not assignable to type 'Data<unknown>' }
如果你定义的
Data是直接的函数类型(type Data<T = unknown> = (data: T) => void),报错逻辑完全一致,只需要去掉示例里的callback包裹层即可。
报错原因
TypeScript 函数参数遵循逆变规则:
- 类型
(data: unknown) => void要求函数可以接收任意类型的入参 variant1/variant2只能接收固定结构的参数,无法满足任意入参的要求,因此不能赋值给Data<unknown>位置- 显式标注
Record<string, Data>等价于Record<string, Data<unknown>>,会强制所有值的泛型参数为统一的unknown,无法单独推导每个key对应的实际类型。
实现方案
方案1:兼容TS4.7,保留显式标注
如果不需要严格的参数类型校验,可以修改Data的默认泛型参数为any,不需要改动f的显式标注即可通过校验:
interface Data<T = any> { callback: (data: T) => void; } const f: Record<string, Data> = { name1: { callback: variant1 }, // 校验通过 name2: { callback: variant2 }, // 校验通过 }
注意:该方案会丢失部分类型安全,调用callback时传入错误参数不会触发报错。
方案2:保留完整类型安全(TS4.7可用)
如果需要保留每个方法的参数类型推导,同时约束所有值符合Data结构,可以通过泛型辅助函数实现,不需要改动类型约束逻辑:
// 泛型辅助函数:校验入参符合Data结构,同时保留每个key的具体类型 function defineDataMap<T extends Record<keyof T, Data<any>>>(map: T): T { return map; } const f = defineDataMap({ name1: { callback: variant1 }, // 自动推导为Data<{hello: number}> name2: { callback: variant2 }, // 自动推导为Data<{hi: string}> });
方案3:TS4.9+ 原生实现
升级到TypeScript 4.9及以上版本,可以用satisfies运算符直接实现类型校验+类型保留,不需要辅助函数:
const f = { name1: { callback: variant1 }, name2: { callback: variant2 }, } satisfies Record<string, Data>;
注意
如果强制要求f必须通过冒号显式标注为Record<string, Data>(即固定为Data<unknown>),在TS现有类型规则下无法保留每个key的具体参数类型——显式标注会将所有值拓宽为统一类型,必然丢失单个条目的泛型信息。
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

