TypeScript函数式对象属性的类型推断问题排查
函数式对象定义中的TypeScript类型推断问题
我在定义函数式对象时遇到了TypeScript类型推断问题,简化后的代码如下:
type TFn<I extends object, R> = { input: I, func: (input: I) => R } type TObj<T extends { [key: string]: [object, any] }> = { [K in keyof T]: TFn<T[K][0], T[K][1]> } function def<T extends { [key: string]: [object, any] }>(formObject: TObj<T>) { return formObject; } const res = def({ fnc1: { input: {hel: 0}, func: (d) => { d.hel // 报错:类型object不存在属性hel return ''; }, }, }); const re = res.fnc1.func({}); // re类型为any,预期应为string
问题点
func的参数d被推断为宽泛的object类型,无法识别input中的具体属性hel- 调用
res.fnc1.func后的返回值类型为any,未能正确推断出预期的string类型
当把[object, any]替换为object并将返回值设为void时,输入部分的类型推断可正常工作,但这无法满足需要自定义返回值类型的场景。
解决方案
问题根源在于原类型约束{ [key: string]: [object, any] }过于宽泛,且通过元组间接映射的方式阻碍了TypeScript的类型关联推断。我们可以调整类型定义,让TypeScript直接追踪每个属性的输入和输出类型:
方案一:简化泛型约束
type TFn<I extends object, R> = { input: I, func: (input: I) => R } // 直接让T对应TFn结构,无需元组中转 function def<T extends Record<string, TFn<object, any>>>(formObject: T) { return formObject; } const res = def({ fnc1: { input: { hel: 0 }, func: (d) => { d.hel; // 正确推断d的类型为{ hel: number } return ''; }, }, }); const re = res.fnc1.func({ hel: 0 }); // re类型正确推断为string
方案二:精确推导输入输出类型
利用条件类型提取func的返回值类型,让TypeScript自动完成更精确的推断:
type TFn<I extends object, R> = { input: I, func: (input: I) => R } function def<T extends Record<string, TFn<any, any>>>(formObject: { [K in keyof T]: TFn<T[K]['input'], T[K]['func'] extends (input: any) => infer R ? R : never> }) { return formObject as T; } const res = def({ fnc1: { input: { hel: 0 }, func: (d) => { d.hel; // 类型推断正常 return ''; }, }, fnc2: { input: { name: 'test' }, func: (d) => d.name.length // 返回值类型自动推断为number } }); const re1 = res.fnc1.func({ hel: 0 }); // string const re2 = res.fnc2.func({ name: 'test' }); // number
原理说明
原代码通过TObj将元组类型映射为TFn,这种间接结构会让TypeScript无法将input的具体类型关联到func的参数上,同时any会丢失返回值的类型信息。调整后直接让泛型T对应TFn的结构,或通过条件类型提取返回值类型,能让TypeScript正确追踪每个属性的输入输出类型,完成精确推断。
内容的提问来源于stack exchange,提问作者Hafil Latheef
相关产品推荐
相关产品推荐

