【问题标题】:Inferring function signature from tuple in Typescript从 Typescript 中的元组推断函数签名
【发布时间】:2020-09-19 07:11:36
【问题描述】:

我现在尝试了很长时间,老实说这不值得,但我仍然想看看是否有解决方案:

我试图强制 TS 从元组中推断出我的函数签名。我尝试使用条件类型,但我似乎没有做对:

// model
interface User {
    name: string
}

interface Article {
    title: string
}

/// api
type Resource = 'user' | 'articles'
type Params = Record<string, number | string | boolean>

type GetSignature =
    | ['user', undefined, User]
    | ['articles', { articleId: number }, Article[]]
    | ['articles', undefined, Article]

// get request
interface ObjArg {
    resource: Resource
    params?: Params
}

type RetType<TResult> = TResult & { cancel: () => void }

async function get<TResult>(args: ObjArg): Promise<RetType<TResult>>
async function get<TResult>(resource: Resource, params?: Params): Promise<RetType<TResult>>
async function get<TResult>(args: [ObjArg | Resource, Params?]): Promise<RetType<TResult>>{
    const { resource, params } = typeof args[0] === 'object' ? args[0] : { resource: args[0], params: args[1] } 
    const result = await someAsyncFetch(resource, params)
    return { ...result, cancel: () => { cancelAsyncFetch() }}
}

我希望 TS 能够从提供的参数中推断出 get 的签名,因此它会自动知道,例如调用 get('articles', { articleId: 1 }) the return type should beArticleas well as that I need the second argument to be of type{articleId: number}(orundefinedfor array of articles). This is whatGetSignature 时应该定义联合类型。

所以期望的用法是这样的

const user = get('user') // returns User
const article = get('articles', { articleId: 1 }) // returns Article
const articles = get('articles') // returns Article[]

我尝试了几十种方法,但似乎没有一种方法能提供我想要的界面。仅提及其中一个,我试图将签名作为类型参数 (get&lt;TSignature exntends GetSignature&gt;(...)) 并试图推断出所需的签名,如下所示:

resource: TSignature[0] extends infer T ? T : never

甚至

resource: TSignature[0] extends infer T ? Extract<GetSignature, T> : never

但似乎没有任何效果。现在我想我会坚持为TResult 提供类型参数,但我想知道是否有办法执行我在 TS 中描述的操作?

【问题讨论】:

    标签: typescript generics tuples conditional-types


    【解决方案1】:

    【讨论】:

      【解决方案2】:

      看起来你希望GetSignature 是这样的:

      type GetSignature =
          | ['user', undefined, User]
          | ['articles', { articleId: number }, Article]
          | ['articles', undefined, Article[]]
      

      (请注意我如何将ArticleArticle[] 交换以匹配您最后的期望)。


      鉴于此,我可能会使用 ExtractExclude 实用程序类型将单参数和双参数签名分开:

      type OneArgSignatures = Extract<GetSignature, [any, undefined, any]>;
      type TwoArgSignatures = Exclude<GetSignature, [any, undefined, any]>;
      

      然后将get() 的调用签名定义为一对重载,一个用于单参数调用,一个用于双参数调用:

      declare function get<R extends OneArgSignatures[0]>(
          resource: R
      ): Extract<OneArgSignatures, [R, any, any]>[2];
      
      declare function get<R extends TwoArgSignatures[0]>(
          resource: R,
          params: Extract<TwoArgSignatures, [R, any, any]>[1]
      ): Extract<TwoArgSignatures, [R, any, any]>[2];
      

      您可以在资源字符串字面量类型R 中看到函数是通用的。请注意,编译器通常最容易从该类型的值推断类型参数。从resource: TSignature[0] extends infer R ? R : never 推断TSignature 是有问题的,但是从resource: R 推断R 很简单。推断出R 后,编译器可以使用Extract 来计算params 的类型(如果存在)和返回类型。


      让我们看看它是否有效:

      const u = get("user"); // User
      const aa = get("articles"); // Article[]
      const a = get("articles", { articleId: 123 }); // Article
      

      看起来不错。请注意,我并不担心Promises、RetType 或您的其他过载。我想你可以解决这个问题。好的,希望有帮助;祝你好运!

      Playground link to code

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-20
        • 2020-08-15
        • 2021-09-22
        • 2018-11-19
        • 1970-01-01
        • 1970-01-01
        • 2021-12-15
        • 2020-07-14
        相关资源
        最近更新 更多