Typescript是否可以基于参数枚举推断类型?

3
假设我有一个函数,它有三个参数。第一个参数是由我定义的枚举类型,第二个参数是一个对象,其形状基于传递给枚举值。第三个参数只是一个字符串。
看一下这个函数:
  async callApi<Response = any, Input = any>(
    op: ApiOperations,
    input: Input,
    functionName: string
  ): Promise<Response> {
    return this.lambda.invoke<Response>(functionName, {
      op,
      input
    });
  }

您可以看到,我将此函数声明为通用函数,并接收两种类型并将默认值设置为任意类型,这很有效。但是,在这种情况下,每当我想调用此函数时,都必须手动指定输入和响应类型。问题是,我知道对于我的ApiOperations枚举中的每个值,我只有一个输入类型和一个响应类型。
因此,我的问题是,typescript是否有办法根据枚举值推断出类型?
调用此函数的示例:
  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[]

1个回答

7
你只需要一个查找类型,将枚举值映射到输入/响应类型。例如:
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']> {
    //...
  }

在这里,通用参数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 }

游乐场


另一种选择是跳过查找类型,而是使用重载,在其中为枚举的每个成员创建函数签名:
  // 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> {
    //...
  }

我个人认为这样做会更加丑陋且难以维护,但这只是个人观点。

游乐场


1
像魔法一样运作!非常感谢您,先生。 - Gustavo Mendonça

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接