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

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; }'.
解决方案

错误原因

  1. 原泛型约束过于严格,要求所有方法的剩余参数必须是unknown[],但实际服务对象的方法剩余参数类型更具体,导致类型不匹配。
  2. 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

代码说明

  1. InjectFirstParam类型:通过映射类型遍历服务对象的每个方法,用条件类型推导出去掉第一个参数后的函数签名。
  2. 泛型约束调整:将S的约束改为Record<string, (first: TypeA, ...args: any[]) => any>,只要求方法的第一个参数是TypeA,剩余参数和返回值不做严格限制,兼容更广泛的服务对象。
  3. 简化实现逻辑:用Object.fromEntries替代reduce,写法更简洁,最后通过类型断言告诉TypeScript最终返回的是我们期望的类型。如果坚持用reduce,可以在赋值时给函数加上类型断言:
acc[key] = ((...params: any[]) => service[key](param1, ...params)) as InjectFirstParam<S>[typeof key];

内容的提问来源于stack exchange,提问作者gandalfml

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 13:24:21