Typescript 是否可以根据参数枚举推断类型?

Gus*_*nça 3 typescript

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

看看这个函数:

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

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

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

调用该函数的一个例子是:

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

这工作正常,但我想要做的方式是:

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

对于这种情况,打字稿将能够告诉我输入的类型GetChatRoomsInputChatRoom[]响应类型。

Ale*_*yne 7

您只需要一个将枚举值映射到输入/响应类型的查找类型。例如:

enum ApiOperations {
  A,
  B,
}

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

  [ApiOperations.B]: {
    input: { b: number }
    response: { b: string }
  }
}
Run Code Online (Sandbox Code Playgroud)

ApiOperationsLookup是一个具有映射到特定输入和响应类型的键名称的类型ApiOperations

您可以使用以下方式获取输入类型:

type Test = ApiOperationsLookup[ApiOperations.A]['input']
// { a: number }
Run Code Online (Sandbox Code Playgroud)

现在你可以callApi像这样:

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

这里的泛型参数T是来自 的值ApiOperations,然后input从该操作的查找映射中提取返回类型。

现在这正如我认为你所期望的那样工作:

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 }
Run Code Online (Sandbox Code Playgroud)

操场


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

  // 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> {
    //...
  }
Run Code Online (Sandbox Code Playgroud)

我认为这对于个人而言是非常丑陋和难以维护的,但这是一个观点问题。

操场