TypeScript映射函数参数时如何保留原函数泛型约束特性
可以完整保留原函数的泛型行为,你当前编写的mapParams类型定义无法实现该效果,核心问题是泛型推断的时机不对,调整类型写法即可达成目标。
原有写法的问题
你当前的mapParams定义把参数类型P声明为了高阶函数本身的泛型参数:
declare function mapParams< I extends (...args: any[]) => any, P extends Parameters<I> >(fn: I): (...args: {[K in keyof P]: SomeWrapper<P[K]>}) => ReturnType<I>;
当你调用mapParams(greet)时,TypeScript会立刻尝试推断P的类型,但此时新函数还没有被调用,拿不到实际传入的参数值,原greet函数的泛型参数Name、Rest只能被推断为约束上限:Name会被固化为"Alice" | "Bob",Rest被固化为[isMother: boolean] | [isFather: boolean],直接丢失了name参数和后续参数的条件关联,也丢失了参数标签的动态变化能力。
正确实现方式
核心思路是把参数类型的泛型推断延迟到返回的新函数被调用时,不要在高阶函数调用阶段就提前解析原函数的参数类型。
首先定义通用的包装类:
class SomeWrapper<T> { constructor(public value: T) {} }
之后调整mapParams的类型定义,将参数泛型声明在返回的函数上,让TypeScript在调用新函数时再做类型推断和原函数泛型的实例化:
declare function mapParams<F extends (...args: any[]) => any>( fn: F ): <Args extends any[]>( ...args: { [K in keyof Args]: SomeWrapper<Args[K]> } ) => F extends (...args: Args) => infer R ? R : never;
效果验证
用上述定义后,你可以获得和原泛型函数完全一致的行为:
注意:你给出的调用示例中第二个参数直接传入true不符合“所有参数被SomeWrapper包裹”的规则,实际需要传入包装后的值才能通过类型校验。
const wrappedGreet = mapParams(greet); // 正确:第二个参数标签提示为isMother,类型校验通过 wrappedGreet(new SomeWrapper("Alice"), new SomeWrapper(true)); // 正确:第二个参数标签提示为isFather,类型校验通过 wrappedGreet(new SomeWrapper("Bob"), new SomeWrapper(true)); // 错误:第二个参数传非boolean类型会报类型不匹配 wrappedGreet(new SomeWrapper("Alice"), new SomeWrapper("invalid value"));
原函数的条件类型约束、参数标签、类型校验逻辑都会完整保留,不会丢失泛型的动态特性。
说明:目前TypeScript没有办法做到对任意结构泛型函数的100%无感知泛型透传(比如带独立返回值泛型、带默认泛型参数的函数需要额外适配),但对于你示例中这类泛型参数完全由入参推断的函数,上述写法可以完美满足需求。
内容的提问来源于stack exchange,提问作者Harry Solovay

