为何TypeScript中pipe调用一处报错另一处正常?修改类型定义后恢复正常的原因
TypeScript pipe函数类型差异问题解析
在以下代码中,pipe(g, f)会触发类型错误,但pipe(g, h)却能正常通过类型检查:
<T, Fn extends (x: T) => number>(f: Fn) => { const g = (x: T) => x; const h = (_: T) => 1; pipe(g, f); // 此处出现类型错误 pipe(g, h); // 类型检查正常通过 };
初始pipe类型定义
export type Func = (..._: any[]) => unknown; export type Unary<Input, Output> = (_: Input) => Output; export type Tail<L extends unknown[]> = L extends readonly [unknown, ...infer LTail] ? LTail : L; type UnaryFn<A, R> = (a: A) => R; type Arg<F> = F extends UnaryFn<infer A, unknown> ? A : never; type Res<F> = F extends UnaryFn<any, infer R> ? R : never; type ValidCompose<F1, F2> = Res<F1> extends (Arg<F2> | Promise<Arg<F2>>) ? F1 : (arg: Arg<F1>) => Arg<F2>; type ValidPipe<FS> = FS extends [infer F1, infer F2, ...infer Rest] ? [ValidCompose<F1, F2>, ...ValidPipe<[F2, ...Rest]>] : FS; export const pipe = <Fs extends Func[]>(...fs: ValidPipe<Fs>) => null
修复后的类型定义
修改以下部分后,所有pipe调用都能正常通过类型检查:
type Arg<F extends Func> = Parameters<F>[0]; type Res<F> = F extends UnaryFn<any, infer R> ? R : never; type ValidCompose<F1 extends Func, F2 extends Func> = Res<F1> extends (Arg<F2> | Promise<Arg<F2>>) ? F1 : (...arg: Parameters<F1>) => Arg<F2>; type ValidPipe<FS extends Func[]> = FS extends [infer F1 extends Func, infer F2 extends Func, ...infer Rest extends Func[]] ? [ValidCompose<F1, F2>, ...ValidPipe<[F2, ...Rest]>] : FS;
问题原因与修复逻辑
初始代码的问题点
Arg<F>类型推断能力不足
初始的Arg<F>通过自定义的UnaryFn类型推断参数:F extends UnaryFn<infer A, unknown> ? A : never。但这个逻辑只能处理非泛型的一元函数,当F是带泛型约束的函数类型(比如示例中的Fn extends (x: T) => number),TypeScript无法准确推断出A的具体类型,导致Res<g>(即T)与Arg<f>的类型匹配失败。
而h是明确的非泛型一元函数,Arg<h>能正确推断为T,所以pipe(g, h)能正常工作。
- 缺乏明确的类型约束
初始的ValidCompose和ValidPipe没有强制泛型参数必须是Func类型,递归处理时TypeScript对函数类型的推断存在模糊性,进一步加剧了类型不匹配的问题。
修改后的修复原理
使用内置工具类型提升推断准确性
用Parameters<F>[0]替代自定义的Arg<F>:Parameters是TypeScript内置工具类型,能直接提取函数的参数元组,无论函数是否带泛型约束,都能准确获取第一个参数的类型。对于示例中的Fn,Parameters<Fn>[0]能正确得到T,解决了初始Arg的推断盲区。添加明确的类型约束
给ValidCompose的F1、F2,以及ValidPipe的F1、F2、Rest都添加extends Func的约束,强制所有参与类型计算的元素都是函数类型,让TypeScript在递归处理时能明确类型边界,避免模糊推断。兼容完整参数列表
将初始的(arg: Arg<F1>) => Arg<F2>改为(...arg: Parameters<F1>) => Arg<F2>,确保返回函数的参数完全匹配原始函数的参数结构,不仅提升了类型安全性,还兼容多参数函数场景。
内容的提问来源于stack exchange,提问作者Uri
相关产品推荐
相关产品推荐

