TypeScript泛型映射服务类型:注入首个参数的函数类型定义
问题描述
我有一个值为函数的对象,示例如下:
const service = { methodA: (param1: TypeA, otherParam: string) => ({ a: 1 }), methodB: (param1: TypeA, otherParam: number) => ({ b: 2 }), }
想要实现一个泛型函数injectFirstParam,为该对象的每个方法传入首个参数,生成每个方法参数数量减一的新服务:
const serviceWithInjectedParam = injectFirstParam(someValue, service) serviceWithInjectedParam.methodA(otherValue) // 最终应调用service.methodA(someValue, otherValue)
但无法正确编写injectFirstParam的类型定义,当前尝试的代码如下:
const injectFirstParam = < S extends { [method: string]: (param1: TypeA, ...params: unknown[]) => unknown } >( param1: TypeA, service: S ) => { return (Object.keys(service) as (keyof S)[]).reduce((acc, key) => { acc[key] = (...params: unknown[]) => service[key](param1, ...params) // 此处参数类型应如何定义?存在TS错误 return acc }, {} as { [key in keyof S]: S[key] extends (param1: TypeA, ...params: infer P) => infer R ? (...params: P) => R : never }) }
TypeScript提示以下错误:
Type '(...params: unknown[]) => unknown' is not assignable to type 'S[keyof S] extends (param1: TypeA, ...params: infer P) => infer R ? (...params: P) => R : never'.
调用injectFirstParam时还出现:
Argument of type '{ methodA: (param1: TypeA, otherParam: string) => { a: number; }; methodB: (param1: TypeA, otherParam: number) => { b: number; }; }' is not assignable to parameter of type '{ [method: string]: (param1: TypeA, ...params: unknown[]) => unknown; }'.
解决方案
错误原因
- 原泛型约束过于严格,要求所有方法的剩余参数必须是
unknown[],但实际服务对象的方法剩余参数类型更具体,导致类型不匹配。 reduce回调里的函数参数用unknown[],无法匹配目标类型中推导的具体参数类型,引发赋值错误。
正确实现代码
// 先定义TypeA的实际类型 type TypeA = { /* 你的TypeA类型定义 */ }; // 定义映射类型,推导去掉第一个参数后的函数类型 type InjectFirstParam<S> = { [K in keyof S]: S[K] extends (first: TypeA, ...rest: infer P) => infer R ? (...rest: P) => R : S[K] }; const injectFirstParam = <S extends Record<string, (first: TypeA, ...args: any[]) => any>>( param1: TypeA, service: S ): InjectFirstParam<S> => { return Object.fromEntries( Object.entries(service).map(([key, fn]) => [ key, (...params: any[]) => fn(param1, ...params) ]) ) as InjectFirstParam<S>; }; // 使用示例 const someValue: TypeA = {}; const service = { methodA: (param1: TypeA, otherParam: string) => ({ a: 1 }), methodB: (param1: TypeA, otherParam: number) => ({ b: 2 }), }; const serviceWithInjectedParam = injectFirstParam(someValue, service); serviceWithInjectedParam.methodA("test"); // 类型校验通过,参数要求为string serviceWithInjectedParam.methodB(123); // 类型校验通过,参数要求为number
代码说明
InjectFirstParam类型:通过映射类型遍历服务对象的每个方法,用条件类型推导出去掉第一个参数后的函数签名。- 泛型约束调整:将
S的约束改为Record<string, (first: TypeA, ...args: any[]) => any>,只要求方法的第一个参数是TypeA,剩余参数和返回值不做严格限制,兼容更广泛的服务对象。 - 简化实现逻辑:用
Object.fromEntries替代reduce,写法更简洁,最后通过类型断言告诉TypeScript最终返回的是我们期望的类型。如果坚持用reduce,可以在赋值时给函数加上类型断言:
acc[key] = ((...params: any[]) => service[key](param1, ...params)) as InjectFirstParam<S>[typeof key];
内容的提问来源于stack exchange,提问作者gandalfml
相关产品推荐
相关产品推荐

