TypeScript中如何约束Record内对象的genSetKey与fn参数匹配?
解决TypeScript中缓存失效器Record的类型推断问题
问题场景
我们需要实现一个由多个invalidator对象组成的Record,每个invalidator包含fn和genSetKey两个函数,要求genSetKey的参数类型必须和对应fn的参数类型完全匹配,用于缓存失效逻辑。
最初的类型定义
一开始写了这两个类型:
type invalidator<TArgs extends unknown[]> = { fn: (...args: TArgs) => any; genSetKey: (...args: TArgs) => string; }; type invalidatorRecord<T extends Record<string, unknown>> = { [K in keyof T]: T[K] extends invalidator<infer U> ? invalidator<U> : never; };
这个定义在显式指定类型时能正确检测参数不匹配的错误,但用工具函数makeInvalidatorRecord自动推断类型时,每个invalidator会被推断成never类型,抛出“类型无法赋值给never”的错误,哪怕对象结构完全符合invalidator的定义。
解决方案
问题出在invalidatorRecord的类型约束和推断逻辑上,我们需要调整工具函数的泛型定义,让TypeScript能正确捕获每个invalidator的参数类型:
修正后的类型与工具函数
基础校验版本
type Invalidator<TArgs extends unknown[]> = { fn: (...args: TArgs) => any; genSetKey: (...args: TArgs) => string; }; // 直接约束Record值为Invalidator类型 type InvalidatorRecord = Record<string, Invalidator<any[]>>; // 工具函数通过泛型自动推断并校验 function makeInvalidatorRecord<T extends InvalidatorRecord>(record: T): T { return record; }
精确类型推断版本
如果需要更严格的类型捕获,确保每个键的参数类型被精确推断:
type Invalidator<TArgs extends unknown[]> = { fn: (...args: TArgs) => any; genSetKey: (...args: TArgs) => string; }; function makeInvalidatorRecord< T extends Record<string, { fn: (...args: any[]) => any; genSetKey: (...args: any[]) => string }> >( record: { [K in keyof T]: T[K] extends { fn: (...args: infer U) => any } ? Invalidator<U> : never; } ): { [K in keyof T]: Invalidator<T[K] extends { fn: (...args: infer U) => any } ? U : never> } { return record as any; }
验证效果
现在使用makeInvalidatorRecord时,TypeScript会自动推断每个invalidator的参数类型,同时严格校验fn和genSetKey的参数一致性:
// 合法示例:参数类型完全匹配 const validRecord = makeInvalidatorRecord({ getUser: { fn: (id: number) => ({ id, name: "Alice" }), genSetKey: (id: number) => `user:${id}` }, getOrder: { fn: (orderId: string, userId: number) => ({ orderId, userId }), genSetKey: (orderId: string, userId: number) => `order:${orderId}:${userId}` } }); // 非法示例:参数类型不匹配,会触发类型错误 const invalidRecord = makeInvalidatorRecord({ getUser: { fn: (id: number) => ({ id, name: "Alice" }), genSetKey: (id: string) => `user:${id}` // 类型错误:参数类型与fn不匹配 } });
原理说明
原来的invalidatorRecord因为T extends Record<string, unknown>的约束过于宽泛,TypeScript无法正确提取每个值的invalidator参数类型,导致推断出never。修正后的工具函数通过更精确的泛型约束和条件类型,让TypeScript能逐个捕获每个键对应的fn参数类型,进而约束genSetKey的参数类型,同时保留自动推断的能力。
内容的提问来源于stack exchange,提问作者lentil-soup
相关产品推荐
相关产品推荐

