TypeScript:如何同时保留传入函数返回类型并推断参数类型?
实现同时保留函数额外属性与参数类型推断
这完全是可行的,并非TypeScript的bug,而是泛型推断逻辑的设计特性导致了之前的两种写法各有局限。我们来拆解问题并给出解决方案:
为什么原有写法存在缺陷
1. testExtends的问题
testExtends定义了两个独立的泛型参数Arg和Fn,其中Fn extends UnaryFunction<Arg, any>。TypeScript会优先从第一个参数arg推断Arg的类型(这里是string),但不会反向从Fn的参数类型去修正Arg。这就导致mapWithCaller中的回调函数参数x无法被正确推断为string(反而被推断为any),进而触发x.length的类型错误;虽然它保留了Fn的完整类型(包括num属性),但参数类型推断失效了。
2. testInferred的问题
testInferred将fn直接约束为UnaryFunction<Arg, Out>,这会让TypeScript把传入的fn(实际是WithNumber类型)“降级”为基础的UnaryFunction类型。因此返回值丢失了WithNumber特有的num属性,导致访问.num时报错。
解决方案:从函数类型中提取参数类型
我们可以通过单个泛型参数捕获完整的函数类型,再用TypeScript内置的Parameters工具类型从该函数类型中提取参数类型,以此约束arg的类型。这样既保留了函数的完整类型(包括额外属性),又能正确推断回调函数的参数类型:
interface UnaryFunction<In, Out> { (arg: In): Out } interface WithNumber<In, Out> extends UnaryFunction<In, Out> { num: number } function mapWithCaller<From, To>(morphism: UnaryFunction<From, To>): WithNumber<From, To> { return Object.assign((x: From) => morphism(x), {num: 5}) } // 正确的实现 function test<Fn extends UnaryFunction<any, any>>(arg: Parameters<Fn>[0], fn: Fn): Fn { return fn; } // 现在既可以正确推断x的类型为string,又能访问num属性 test('1', mapWithCaller(x => x.length)).num; // 完全正常,无类型错误
原理说明
- 泛型
Fn会直接捕获传入函数的完整类型(这里是WithNumber<string, number>),保留了num属性和函数签名。 Parameters<Fn>[0]从Fn的函数签名中提取出参数类型(string),让arg的类型与fn的参数类型严格对齐,从而触发TypeScript对mapWithCaller回调函数参数x的正确推断。
这种写法利用了TypeScript的类型推断优先级和工具类型的能力,完美解决了你的需求。
内容的提问来源于stack exchange,提问作者hesxenon
相关产品推荐
相关产品推荐

