TypeScript泛型问题:如何基于回调返回值推断对象类型?
解决TypeScript中transform函数泛型类型推断问题
问题场景
我实现了一个用于不可变转换对象属性的工具函数transform,它接收原始对象和转换配置对象(配置对象的每个属性对应一个转换函数),代码如下:
type ChangeProperties<T extends object, U extends keyof T, S extends Record<U, any>> = { [P in Exclude<keyof T, U>]: T[P] } & S; export const transform = <T extends object, U extends keyof T, S extends Record<U, any>>( stat: T, transformConf: { [P in U]: (val: T[P]) => S[P] }, ) => { return { ...stat, ...(Object.keys(transformConf) as U[]).reduce((acc, key) => { acc[key] = transformConf[key](stat[key]); return acc; }, {} as Record<U, any>), } as unknown as ChangeProperties<T, U, S>; }; // 用法示例 transform({a: 1, b: "2", c: [1, 2, 3]}, { b: val => Number(val), c: val => Math.max(...val) }); // 预期返回类型为 { a: 1, b: 2, c: 3 }
但TypeScript无法正确推断泛型参数S的类型(预期为{ b: number, c: number }),请问该如何解决?这是否与上下文类型和上下文敏感函数有关?
问题原因
确实和上下文类型相关。原泛型结构中,S被约束为Record<U, any>,而U是从transformConf的键名推断而来,形成了循环依赖:TypeScript需要先确定U才能约束S,但S的类型又依赖转换函数的返回值,原结构无法让TypeScript从转换函数返回值反向推断S。同时,虽然transformConf中的函数参数val能通过上下文类型推断出T[P],但返回值类型无法关联到S[P],导致S的推断失败。
解决方案
调整泛型参数结构,让TypeScript直接从转换函数的返回值推断转换后的属性类型,而非先约束S。具体代码如下:
// 重新定义结果类型:保留原对象未转换的属性,合并转换后的属性 type TransformResult<T extends object, U extends Partial<Record<keyof T, any>>> = Omit<T, keyof U> & U; export const transform = <T extends object, U extends Partial<Record<keyof T, any>>>( stat: T, transformConf: { [K in keyof U]: (val: T[K]) => U[K] } ): TransformResult<T, U> => { // 生成转换后的属性对象 const transformedProps = Object.fromEntries( Object.entries(transformConf).map(([key, transformFn]) => { return [key, transformFn(stat[key as keyof T])]; }) ) as U; // 合并原对象与转换后的属性(不可变操作) return { ...stat, ...transformedProps }; }; // 用法示例 const result = transform({a: 1, b: "2", c: [1, 2, 3]}, { b: val => Number(val), c: val => Math.max(...val) }); // result类型被正确推断为:{ a: number; b: number; c: number }
关键调整点
- 泛型参数简化:将原有的
U和S合并为单个泛型U,直接代表转换后的属性类型集合,TypeScript可从转换函数的返回值自动推断U。 - 结果类型优化:使用
Omit<T, keyof U> & U替代原ChangeProperties类型,更简洁且符合TypeScript的类型合并规则。 - 实现逻辑优化:用
Object.fromEntries替代reduce,代码更简洁易读。
补充说明
调整后的代码既保留了原函数的不可变特性,又让TypeScript能正确推断所有类型:
- 转换函数的参数
val仍能通过上下文类型推断出原对象对应属性的类型; - 转换后的结果类型会自动合并原对象未转换的属性与转换后的属性类型。
内容的提问来源于stack exchange,提问作者crazyones110
相关产品推荐
相关产品推荐

