打字稿泛型类型推断

时间:2021-03-02 13:30:07

标签: typescript typescript-typings typescript-generics

我正在通过实现强类型的休息请求机制来玩打字稿。

让代码说话:

使用这种类型,我想定义路由和相关对象类型之间的映射:

export interface RoutesMapping {
    api1: {
        users: UserApiModel
        products: ProductApiModel,
    }
    api2: {
        "other-route": OtherModel1,
        "another-one-route": OtherModel2
    }
}

export type ApiScope = keyof RoutesMapping

以下函数是我愿意用来发出 POST 请求的函数

export type RestApiPostResponse<T = any> = {
    result: boolean
    data: T
}

export function restPost<S extends keyof RoutesMapping = "api1", T extends keyof RoutesMapping[S] = keyof RoutesMapping[S]>(
  route: T,
  // nervermind this object, is out of scope for the question
  options: ApiRequestOptions<S, any> = {}
): Promise<RestApiPostResponse<RoutesMapping[S][T]>> {
  const url = apiUtils.buildUrl(route as string, options)
  const { payload, headers, isProtected } = options
  return post({
    url,
    isProtected,
    payload,
    headers
  });
}

我希望通过以下方式调用这个函数

const data = await restPost("users")

make 打字稿通过范围和路由推断返回类型。

实际上,将它与默认类型参数一起使用是可行的:

enter image description here

问题是当我想以这种方式调用其他api时:

const data = await restPost<"api2">("other-route")

不幸的是,它不起作用,它推断出所有可能的类型

enter image description here

解决问题的唯一方法是显式添加第二个类型参数

enter image description here

如何使用所有这些而不需要在第二个场景中添加第二个类型参数?

这是一个typescript playground

1 个答案:

答案 0 :(得分:1)

如果你推断出 api-key 类型参数,你实际上可以构建一个你想要的解决方案:

type Model<Route> = // Returns the model value for key Route in RoutesMapping
  keyof RoutesMapping extends infer Api
  ? Api extends keyof RoutesMapping 
    ? Route extends keyof RoutesMapping[Api]
      ? RoutesMapping[Api][Route]
      : never
    : never
  : never

type Routes<Api> = Api extends {} ? keyof Api  : never // Helper to distribute keyof over a union of objects
type AllRoutes = Routes<RoutesMapping[keyof RoutesMapping]> // Union of all route keys: 'users' | 'products' | 'other-route' | 'another-one-route'

export function restPost<Route extends AllRoutes>(
  route: Route,
  options?:{url:string,payload:any}
): Promise<RestApiPostResponse<Model<Route>>> {
 ..
}

当应用于路由字符串时,会推断出 restPost 的正确返回类型,而无需指定任何类型参数:

const data = await restPost("users") // data: RestApiPostResponse<UserApiModel>
const data2 = await restPost("other-route") // data2: RestApiPostResponse<OtherModel1>

TypeScript playground

请注意,这里假设路由键是唯一的,这似乎是因为 api 键没有传递给 restPost。我也不确定引入所有这些复杂性是否明智,但至少是可能的。