从Typescript中的元组推断函数签名

Inferring function signature from tuple in Typescript

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

我试图强制 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` union type should define.

因此所需的用法类似于

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

我尝试了几十种方法,none 似乎提供了我想要的界面。仅举其中之一,我试图将签名作为类型参数 (get<TSignature exntends GetSignature>(...)) 并尝试推断所需的签名,如下所示:

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

甚至

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

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

你可以试试这个:


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

declare function get<K extends keyof GetSignature>(type: K): Promise<RetType<GetSignature[K][0]>>
declare function get<K extends keyof GetSignature>(type: K, param: GetSignature[K][1]): Promise<RetType<GetSignature[K][2]>>
async function get(type, param) {
  ...
}

操场: https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBAbwL4CgUHp1wLYQCbAA2KAlgHYzBQBmAhgMbBwCqAzlYinN3GbdsABccVjCjkA5ilSkKVOozgBBWCXqEmCLjxgkYG4aPFkpMzFlpgSKGAE8wTAErBWEAK5RFAXjgByN+xQvnAAPn60quouvjb2TAAKEfyscD7O9NB4ADxGkgA0vG7YAEYcYbkmoXDFEBAatGQAfLEOcM4wACpxWR3OrG6EMI2pcL0uA-AAZIhw9A2MhMIAFACUqcMAbhAkeHAydq0A4sAwAMokEnwwHkw+WtwBVMIA2mxUBW5kBNTkwHgFbygAF1tBFdFFWC8VOCNAUEHAwWoNABJPDCMhFUpQPYFaFI4DPIEgmQEdQRJjUT70XQQMhwCQnLIAaTgoEoXxSAGtgLYINQ4MczhcrjdGksDkI4EyVsJ4lAINgSOwsu0ug4soLzpdaNcoASmUDngAGIGNZqkwjkuCUsjUki0+mMlls4AcuDc3n8zXCnWi8VxYRMgpgJLYYTe7W6-WGgCMQJlcDlCqVwBVJzVqYjIr1zwNzwATKbmiglrQAO60PSOmBLfyBXwrFYAOj4AibNUIeFWAG4S+XK-AGTXfIiIQ3m7p9MB23Uuyte6WK1Wh7XRxpWL44WvgKjhDGkI3jUCm5ONDPOz2gA

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

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

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


鉴于此,我可能会使用 Extract and Exclude 实用程序类型分离出一个参数和两个参数的签名:

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 的类型(如果存在)和 return 类型。


让我们看看它是否有效:

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

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

Playground link to code