You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:32:04