TypeScript中函数数组reduce及通用pipe函数类型定义问题
解决TypeScript Pipe函数的类型推导问题
我完全懂你遇到的这个痛点——TypeScript对这种链式函数调用的类型推导确实不太友好,尤其是想用reduce实现pipe的时候,默认没法自动追踪每一步函数的输入输出类型关联。你尝试的递归写法思路是对的,但没把剩余函数的类型和前一个函数的返回值绑定起来,所以类型推导失效了。
正确的实现方案:用递归条件类型追踪类型链
我们可以先定义一个递归条件类型来处理函数链的类型流转,再用它来约束pipe函数的输入输出:
第一步:定义辅助类型PipeFns
这个类型会递归遍历函数元组,把每一步函数的返回值作为下一个函数的输入类型,最终返回最后一个函数的输出类型:
type PipeFns<Input, Fns extends readonly ((input: any) => any)[]> = // 检查函数元组是否非空 Fns extends [infer FirstFn, ...infer RestFns] ? // 确认第一个函数的输入是当前的Input类型,提取它的返回值R FirstFn extends (input: Input) => infer R ? // 递归处理剩余函数,把R作为新的Input PipeFns<R, RestFns> : never : // 函数元组为空时,返回当前的Input类型(也就是最后一步的返回值) Input;
第二步:实现带类型推导的pipe函数
用上面的辅助类型来约束函数的参数和返回值:
function pipe<Source, Fns extends readonly ((input: any) => any)[]>( source: Source, ...fns: Fns ): PipeFns<Source, Fns> { // 用reduce执行函数链,这里需要类型断言,因为TypeScript没法自动推导reduce的复杂类型链 return fns.reduce((currentValue, fn) => fn(currentValue), source) as PipeFns<Source, Fns>; }
可选:递归函数写法的类型修正
如果你更倾向于递归执行而不是reduce,可以用函数重载+递归类型的组合,让类型推导更精准:
// 重载:没有函数时直接返回源值 function pipe<Source>(source: Source): Source; // 重载:一个函数时返回该函数的输出类型 function pipe<Source, R1>(source: Source, fn1: (input: Source) => R1): R1; // 重载:两个函数时返回第二个函数的输出类型 function pipe<Source, R1, R2>(source: Source, fn1: (input: Source) => R1, fn2: (input: R1) => R2): R2; // 通用重载:支持任意长度的函数链 function pipe<Source, Fns extends readonly ((input: any) => any)[]>( source: Source, ...fns: Fns ): PipeFns<Source, Fns> { if (fns.length === 0) return source as PipeFns<Source, Fns>; const [firstFn, ...restFns] = fns; return pipe(firstFn(source), ...restFns) as PipeFns<Source, Fns>; }
测试一下类型推导效果
用一个示例验证类型是否正确推导:
const result = pipe( 10, // 初始值:number (n) => n.toString(), // 输入number,输出string (str) => str.length, // 输入string,输出number (len) => len > 2 // 输入number,输出boolean ); // 鼠标hover到result上,会看到类型被正确推导为boolean console.log(result); // 输出false
为什么这个方案可行?
TypeScript的类型系统本身不会自动追踪可变元组中每个元素的依赖关系,而我们定义的PipeFns通过递归遍历元组,强制把每一步函数的输入类型和前一步的输出类型绑定,从而让TypeScript能正确推导最终的返回类型。类型断言as PipeFns<Source, Fns>是必要的,因为reduce的类型推导能力有限,没法自动识别这种复杂的链式关联。
内容的提问来源于stack exchange,提问作者Rúnar Berg
相关产品推荐
相关产品推荐

