簡體   English   中英

允許多個不同形狀的接口作為 TypeScript 返回類型

[英]Allowing multiple differently shaped interfaces as TypeScript return types

我有一個函數,它接受一些參數並生成將傳遞到外部進程的對象。 因為我無法控制最終需要創建的形狀,所以我必須能夠將一些不同的參數帶到我的函數中並將它們組合成適當的對象。 這是一個非常基本的示例,展示了我遇到的問題:

interface T1A {
    type: 'type1';
    index: number;
}

interface T1B {
    type: 'type1';
    name: string;
}

interface T2A {
    type: 'type2';
    index: number;
}

interface T2B {
    type: 'type2';
    name: number;
}

function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
    return {type, index: 3}
}

這個特殊的函數抱怨“type1”不能分配給“type2”。 我閱讀了一些關於類型保護的內容,並想出了如何讓這個簡單的例子變得有趣:

function hello(type: 'type1' | 'type2'): T1A | T1B | T2A | T2B {
    if (type === 'type1') {
        return {type, index: 3}
    } else {
        return {type, index: 3}
    }
}

但是,我不明白為什么這是必要的。 類型檢查對返回值的可能性沒有任何作用。 基於我的參數僅采用兩個顯式字符串之一的事實,單個 return 語句保證返回 T1A 或 T2A 之一。 在我的實際用例中,我有更多的類型和參數,但我分配它們的方式總是保證至少返回一個指定的接口。 在處理接口時,聯合似乎並不是真正的“這個或這個”。 我試圖分解我的代碼來處理每個單獨的類型,但是當有 8 種不同的可能性時,我最終會得到很多似乎無用的額外 if/else 塊。

我也試過使用類型type T1A = {...};

我是不是對工會有什么誤解,或者這是一個錯誤,還是一種更簡單的處理方法?

通常,TypeScript 不會執行從屬性向上傳播聯合的類型。 也就是說,雖然{foo: string | number}類型的每個值 {foo: string | number}應該可以分配給類型{foo: string} | {foo: number} {foo: string} | {foo: number} ,編譯器不會將它們視為可相互分配的:

declare let unionProp: { foo: string | number };
const unionTop: { foo: string } | { foo: number } = unionProp; // error!

除了當您開始改變此類類型的屬性時會發生奇怪的事情之外,編譯器通常需要做太多工作才能一直這樣做,尤其是當您有多個聯合屬性時。 microsoft/TypeScript#12052 中的相關評論說:

這種等價性僅適用於具有單個屬性的類型,在一般情況下並非如此。 例如,考慮{ x: "foo" | "bar", y: string | number }是不正確的{ x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number }等價於{ x: "foo", y: string } | { x: "bar", y: number } { x: "foo", y: string } | { x: "bar", y: number }因為第一種形式允許所有四種組合,而第二種形式只允許兩種特定的組合。

所以它本身不是一個錯誤,而是一個限制:編譯器只會花很多時間來處理聯合以查看某些代碼是否有效。


在 TypeScript 3.5 中, 添加了專門針對可區分聯合的情況進行上述計算的支持 如果您的聯合具有可用於在聯合的至少某個成員中區分聯合成員(這意味着單例類型,如字符串文字數字文字undefinednull )的屬性,那么編譯器將驗證您的代碼你想要的方式。

這就是為什么如果您更改T1A | T2A | T1B | T2B會突然起作用的原因T1A | T2A | T1B | T2B T1A | T2A | T1B | T2B T1A | T2A | T1B | T2BT1A | T2A T1A | T2A ,可以回答您的問題:

function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
    const x: T1A | T2A = { type, index: 3 }; // okay
    return x;
}

后者是一個有區別的聯合: type屬性告訴你你有哪個成員。 但前者不是: type屬性可以區分T1A | T1B T1A | T1BT2A | T2B T2A | T2B ,但沒有進一步細分的屬性。 不,類型中僅缺少indexname不能算作判別式,因為類型T1A的值可能具有name屬性; TypeScript 中的類型並不精確

上面的代碼有效是因為編譯器可以驗證x的類型為T1A | T2A T1A | T2A ,然后可以驗證T1A | T2A T1A | T2A是完整T1A | T2A | T1B | T2B的亞型T1A | T2A | T1B | T2B T1A | T2A | T1B | T2B T1A | T2A | T1B | T2B 因此,如果您對兩步流程感到滿意,那么這對您來說是一個可能的解決方案。


如果你想要T1A | T2A | T1B | T2B T1A | T2A | T1B | T2B T1A | T2A | T1B | T2B要成為可區分的聯合,您需要修改組成類型的定義,以便真正區分至少一個公共屬性。 比如說,這個:

interface T1A {
    type: 'type1';
    index: number;
    name?: never;
}

interface T1B {
    type: 'type1';
    name: string;
    index?: never;
}

interface T2A {
    type: 'type2';
    index: number;
    name?: never;
}

interface T2B {
    type: 'type2';
    name: number;
    index?: never;
}

通過添加never類型的可選屬性,您現在可以使用typenameindex作為判別式。 nameindex屬性有時會是undefined ,這是一種支持區分類型的單例類型。 然后這有效:

function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
    return { type, index: 3 }; // okay
}

所以這是兩種選擇。 在您知道某些東西是安全的但編譯器不安全的情況下,另一個始終可用的選項是使用類型斷言

function hello2(type: 'type1' | 'type2') {
    return { type, index: 3 } as T1A | T2A | T1B | T2B
}

好的,希望有幫助; 祝你好運!

Playground 鏈接到代碼

暫無
暫無

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

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