如何在TypeScript中正确为包含同输入输出类型的泛型函数的Record进行类型定义?
如何在TypeScript中正确为包含同输入输出类型的泛型函数的Record进行类型定义?
我太懂你这个痛点了——用InOutFunc<any>确实会把整个Record的类型安全彻底搞崩,而且你说的协变逆变问题完全说到点子上了:InOutFunc<T>在T上是不变类型,输入参数要逆变、返回值要协变,根本找不到一个统一的T能同时罩住所有函数的输入输出约束。
先给你上最直接的解决方案,先把你当前的代码救回来:
方案1:用映射类型精准定义具体Record类型
如果你已经明确知道Record里每个键对应的T类型(比如foo是number,bar是string),用映射类型可以给每个函数单独指定类型,完全不会丢失信息:
export type InOutFunc<T> = (input: T) => T; // 定义一个泛型Record类型,每个键对应自己的InOutFunc type InOutRecord<T extends Record<string, unknown>> = { [K in keyof T]: InOutFunc<T[K]>; }; // 针对你的场景,写具体的Record类型 type MyInOutRecord = InOutRecord<{ foo: number; bar: string; }>; // 现在赋值的时候,类型检查严丝合缝 const inOutRecord: MyInOutRecord = { foo: (a: number): number => 1, bar: (a: string): string => 'abc', }; // 访问foo的时候,类型还是(number) => number,完全保留了原来的类型 inOutRecord.foo(123); // 必须传number,传别的直接报错 inOutRecord.bar('test'); // 同理,必须传string
方案2:让TypeScript自动推断类型(不用手动写每个键的T)
如果不想提前写死每个键的T类型,用泛型函数来创建Record就行,TypeScript会自动帮你推断每个函数的T:
export type InOutFunc<T> = (input: T) => T; // 写个泛型函数,专门用来创建这种Record function createInOutRecord<T extends Record<string, unknown>>( record: { [K in keyof T]: InOutFunc<T[K]> } ) { return record; } // 直接传对象,TypeScript会自动识别foo是number、bar是string const inOutRecord = createInOutRecord({ foo: (a: number): number => 1, bar: (a: string): string => 'abc', }); // 用的时候类型完全安全,比如foo的返回值还是number const fooResult = inOutRecord.foo(456); const barResult = inOutRecord.bar('hello');
为啥原来的写法不行?
你之前用Record<string, InOutFunc<any>>的问题,本质是any直接把所有具体类型都抹没了——不管传什么参数、返回什么值,TypeScript都不会管。而且你说的协变逆变冲突确实是核心:InOutFunc<T>是不变类型,输入要逆变、返回要协变,所以:
- 用
InOutFunc<never>的话,输入根本传不了值,但返回值也被锁死成never,完全没用; - 用
InOutFunc<unknown>的话,返回值是unknown(得手动断言才能用),输入又能传任意值,完全不安全;
这俩根本没法兼容,所以必须放弃“用一个统一T覆盖所有函数”的思路,转而给每个键单独指定或推断T类型。
处理通用场景的小技巧
如果要写一个能处理任意这类Record的通用函数,别用any或unknown,用泛型就能保留完整的类型安全:
function processInOutRecord<T extends Record<string, unknown>>( record: { [K in keyof T]: InOutFunc<T[K]> } ) { // 这里处理每个函数的时候,TypeScript都知道每个键对应的T类型 Object.entries(record).forEach(([key, func]) => { // 比如如果是foo,func的类型就是(number) => number,完全安全 }); }
这样不管你传入什么样的InOutRecord,函数内部都能保持类型安全,不会出现类型丢失的问题。
内容来源于stack exchange
相关产品推荐
相关产品推荐

