打字稿是否可以根据参数枚举推断类型?

Typescript is it possible to infer types based on argument enum?

假设我有一个有 3 个参数的函数。第一个参数是我定义的枚举,第二个参数是一个对象,其形状基于枚举传递的值。第三个参数只是一个字符串。

看看这个函数:

  async callApi<Response = any, Input = any>(
    op: ApiOperations,
    input: Input,
    functionName: string
  ): Promise<Response> {
    return this.lambda.invoke<Response>(functionName, {
      op,
      input
    });
  }

您可以看到我将此函数声明为通用函数并接收两种类型并将默认值设置为任意类型,这很有效。但在这种情况下,每当我想调用此函数时,我都必须手动指定输入和响应类型。问题是,我知道对于 ApiOperations 枚举中的每个值,我只有一种输入类型和一种响应类型。

所以我的问题是,打字稿有什么方法可以根据枚举值推断类型吗?

调用此函数的示例是:

  async getChatRooms({ numberResults, siteId, startIndex }: GetChatRoomsInput): Promise<ChatRoom[]> {
    return this.api.callApi<ChatRoom[], GetChatRoomsInput>(ApiOperations.GetChatRooms, {
      siteId,
      numberResults,
      startIndex
    }, 'getChatRooms');
  }

这很好用,但我想做的是:

  async getChatRooms({ numberResults, siteId, startIndex }: GetChatRoomsInput): Promise<ChatRoom[]> {
    return this.api.callApi(ApiOperations.GetChatRooms, {
      siteId,
      numberResults,
      startIndex
    }, 'getChatRooms');
  }

对于这种情况,typescript 可以告诉我输入的类型是 GetChatRoomsInput,响应类型是 ChatRoom[]

您只需要一个将枚举值映射到 input/response 类型的查找类型。例如:

enum ApiOperations {
  A,
  B,
}

interface ApiOperationsLookup {
  [ApiOperations.A]: {
    input: { a: number }
    response: { a: string }
  }

  [ApiOperations.B]: {
    input: { b: number }
    response: { b: string }
  }
}

这里 ApiOperationsLookup 是一个类型,它具有 ApiOperations 的键名映射到特定的输入和响应类型。

您可以使用以下内容获取输入类型:

type Test = ApiOperationsLookup[ApiOperations.A]['input']
// { a: number }

现在你可以让 callApi 看起来像这样:

  async callApi<T extends ApiOperations>(
    op: T,
    input: ApiOperationsLookup[T]['input'],
    functionName: string
  ): Promise<ApiOperationsLookup[T]['response']> {
    //...
  }

此处通用参数 TApiOperations 的值,然后 input 和 return 类型从该操作的查找映射中提取。

这现在可以正常工作了,我想你是这样的:

const aResponse = await instance.callApi(ApiOperations.A, { a: 123 }, 'aFuncName')
// { a: string }

const aBadResponse = await instance.callApi(ApiOperations.A, { b: 123 }, 'aFuncName')
// type error on second argument

const bResponse = await instance.callApi(ApiOperations.B, { b: 123 }, 'aFuncName')
// { b: string }

Playground


另一种选择是跳过查找类型,而是使用重载,为枚举的每个成员创建函数签名:

  // signature for each enum member
  async callApi(op: ApiOperations.A, input: { a: number }, functionName: string): Promise<{ a: string }>
  async callApi(op: ApiOperations.B, input: { b: number }, functionName: string): Promise<{ b: string }>

  // implementation
  async callApi(
    op: ApiOperations,
    input: unknown,
    functionName: string
  ): Promise<unknown> {
    //...
  }

我个人认为这是一个很大的丑陋和难以维护的问题,但这是一个见仁见智的问题。

Playground