从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',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 be Article as well as that I need the second argument to be of type {articleId:number} (or未定义for array of articles). This is what时应定义GetSignature`联合类型。

因此所需的用法类似于

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

我尝试了数十种方法,但似乎都没有提供我想要的界面。仅提及其中之一,我试图将签名作为类型参数(get<TSignature exntends GetSignature>(...))进行尝试,并试图像这样的那样推断所需的签名:

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

甚至

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

但是似乎没有任何效果。现在,我想我会坚持为TResult提供type参数,但是我想知道是否有一种方法可以完成我在TS中描述的事情?

iCMS 回答:从Typescript

您可以尝试以下方法:


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',User]
    | ['articles',Article]
    | ['articles',Article[]]

(请注意,我是如何将ArticleArticle[]交换的,以便与您最终期望的匹配。)


鉴于此,我可能会使用ExtractExclude实用程序类型来分离出一个参数和两个参数:

type OneArgSignatures = Extract<GetSignature,[any,any]>;
type TwoArgSignatures = Exclude<GetSignature,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,any]>[1]
): Extract<TwoArgSignatures,any]>[2];

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


让我们看看它是否有效:

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

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

Playground link to code

本文链接:https://www.f2er.com/2231255.html

大家都在问