簡體   English   中英

如何使用字符串文字類型使 function 重載?

[英]How to make function overloading with string literal types?

我不確定標題,如果有人有更好的信息,請告訴我。

我有這種類型:

export type FooMedia = "course" | "article" | "podcast";

然后我有一個 function 接收該類型的字符串並基於傳遞的字符串具有不同的返回類型。

重載:


function getFoo(
  id: string,
  type: "article"
): Promise<AxiosResponse<Article, any>>;

function getFoo(
  id: string,
  type: "podcast"
): Promise<AxiosResponse<Podcast, any>>;

function getFoo(
  id: string,
  type: "course"
): Promise<AxiosResponse<Course, any>>;

Function 實現:


function getFoo<T extends FooMedia, R extends Media>(id: string, type: T> {
...
return axios.get<R>(endpoint);
}

但是,當我像這樣在 function 中調用它時:

function bar(type: FooMedia) {
 return getFoo("t", type);
}

我收到一條錯誤消息

FooMedia 不可分配給“文章”類型的參數(以及“播客”“課程”類型的 rest)。

預期的行為是基於傳遞給調用的FooMedia類型,我得到正確的返回類型。

這可能嗎?

想要做的是在type參數的類型中使bar()泛型,如下所示:

function bar<K extends FooMedia>(type: K) {
  return getFoo("t", type); // error!
}

但不幸的是,TypeScript 並不真正知道如何對重載函數執行高階分析,以便弄清楚如何處理getFoo("t", type) for generic type

重載適用於當您使用靜態已知以匹配調用簽名之一的代碼顯式調用它的情況。 其他任何事情都遠遠超出了編譯器的能力。

您可以調用getFoo("", "article")很好,因為編譯器可以將其解析為getFoo(id: string, type: "article"): Promise<Article>; 來電簽名。

但是getFoo("", Math.random()<0.5? "article": "course")不行 你得到一個錯誤,沒有重載與調用匹配。 編譯器不會將第二個參數的聯合類型傳播到調用簽名的聯合(這是在microsoft/TypeScript#14107處請求的)。 同樣的問題也發生在通用typegetFoo("", type)上。 由於不知道type恰好是一個特定的可接受值,因此編譯器不知道要使用哪個調用簽名,因此它放棄了。

您可以通過添加適用於該類型的聯合類型的調用簽名來“修復”它並避免bar()中的錯誤:

// add this signature
function getFoo(
  id: string, type: FooMedia
): Promise<AxiosResponse<Course | Article | Podcast, any>>;

但是 TypeScript 不會從特定的調用簽名中合成或推斷通用調用簽名,因此您會得到一個聯合:

function bar<K extends FooMedia>(type: K) {
  return getFoo("t", type); // no error, but
}

const z = bar("course");
// const z: Promise<AxiosResponse<Course | Article | Podcast, any>>

不幸的是,重載不是這項工作的工具。


如果您希望bar()是通用的並讓它調用getFoo() ,那么getFoo()也應該是通用的。 您幾乎總是可以將重載的 function 調用簽名集轉換為單個通用簽名集,但在一般情況下,這有點煩人。 假設你有這個:

declare function ovld(x: string, y: number): boolean;
declare function ovld(x: number): string;
declare function ovld(x: boolean, y: string): number;

ovld(3).toUpperCase();
ovld(true, "abc").toFixed(2);

您可以重構為輸入輸出映射類型,例如

type CallSignatures =
  { i: [x: string, y: number], o: boolean } |
  { i: [x: number], o: string } |
  { i: [x: boolean, y: string], o: number };

並編寫一個條件類型來提取給定輸入的 output:

type Out<C extends { i: any[], o: any }, I extends any[]> =
  C extends unknown ? I extends C['i'] ? C['o'] : never : never;

並測試一下:

declare function gnrc<I extends CallSignatures['i']>(...args: I): Out<CallSignatures, I>;
gnrc(3).toUpperCase();
gnrc(true, "abc").toFixed(2);

這行得通,但它有點惡心。 但是在這種情況下,我們可以大大簡化。 除了單個字符串文字類型輸入參數外,所有調用簽名看起來都相同。 並且 output 類型也相同,除了該字符串文字輸入類型的 function 類型。 TypeScript 已經非常支持從字符串文字輸入到任意輸出的映射......它被稱為接口

interface FooMap {
  course: Course,
  article: Article,
  podcast: Podcast
}
type FooMedia = keyof FooMap;

declare function getFoo<K extends FooMedia>(
  id: string,
  type: K
): Promise<AxiosResponse<FooMap[K], any>>;

這很簡單。 現在,如果您願意,您仍然可以將其實現為重載,盡管它具有單個調用簽名:

// call signature
function getFoo<K extends FooMedia>(
  id: string,
  type: K
): Promise<AxiosResponse<FooMap[K], any>>;

// implementation
function getFoo(id: string, type: FooMedia) {
  return null!;
}

當編譯器無法驗證實現與調用簽名匹配時,人們會這樣做,因為重載實現的檢查比常規的 function 實現更寬松。 好的,讓我們看看bar()現在是否有效:

function bar<K extends keyof FooMap>(type: K) {
  return getFoo("t", type);
}

// function bar<K extends keyof FooMap>(
//   type: K): Promise<AxiosResponse<FooMap[K], any>>

推斷的返回類型現在很好且通用,這意味着調用bar()應該會產生您想要的類型:

const z = bar("course");
// const z: Promise<AxiosResponse<Course, any>>

萬歲!

Playground 代碼鏈接

您需要為您的一般表格添加額外的聲明。

與此類似的東西:

function getFoo(id: string, type: FooMedia): Promise<AxiosResponse<Media, any>>;

這是一個完整的例子。 我假設Media是聯合類型。

type Article = {}
type Podcast = {}
type Course = {}

type Media = Article | Podcast | Course;

export type FooMedia = "course" | "article" | "podcast";

function getFoo(
  id: string,
  type: "article"
): Promise<Article>;
function getFoo(
  id: string,
  type: "podcast"
): Promise<Podcast>;
function getFoo(
  id: string,
  type: "course"
): Promise<Course>;
function getFoo(id: string, type: FooMedia): Promise<Media>;
function getFoo<T extends FooMedia, R extends Media>(id: string, type: T) {
    return {id} as unknown as R
}

function bar(type: FooMedia) {
 return getFoo("t", type);
}

操場

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM