簡體   English   中英

Typescript 檢查數組值在 enum1 或 enum 2 但不是兩者

[英]Typescript check array value is in enum1 or enum 2 but not both

是否可以檢查myList中的值是否來自Enum1Enum2但不能同時來自兩者?

enum Enum1 {
  Foo = '1',
}
    
enum Enum2 {
  Bar = '1',
}
    
const myList = (Enum1 | Enum2)[] = [
  Enum1.Foo, // not wanted - '1' appears in Enum1 and Enum2
]

首先, TypeScript中的 union 是inclusive而不是Exclusive A 工會A | B A | B包括交叉點A & B 換句話說,聯合A | B A | B接受A類型的所有值和B類型的所有值,包括AB類型的任何值。

看起來您正在嘗試表示排他性聯合,它專門排除了交集。 就像是

const myList: ExclusiveUnion<Enum1, Enum2>[] = [ /* ... */ ];

不幸的是,您不能寫出適用於所有類型TU的完全通用的ExclusiveUnion<T, U>類型 function 。 TypeScript 不直接支持否定類型(請參閱microsoft/TypeScript#29317 ),因此沒有not明確排除類型的類型運算符。 如果not存在,你可以這樣寫:

// none of this is valid TypeScript, don't try it:
type ExclusiveUnion<T, U> = (T | U) & (not (T & U));
type ExclusiveUnion<T, U> = (T & not U) | (U & not T);

但它沒有,所以你不能。


但是:對於TU本身是文字類型聯合的特殊情況(如"1" | "2" | null | 3 | undefined | false ),您可以使用Exclude實用程序類型過濾掉T | U T | U可分配給交點T & U

// this works only when T | U are unions of literal types 
type ExclusiveUnion<T, U> = Exclude<T | U, T & U>

因此,至少從概念上講,您可以編寫類似

type Enum1XorEnum2 = ExclusiveUnion<Enum1, Enum2>

因為Enum1Enum2類型都應該類似於文字字符串/數字類型的聯合。 這在概念上是可行的,但在實踐中卻並非如此。 問題是枚舉很奇怪。


TypeScript 中的枚舉值有一些相當奇怪的行為; 有關阻止上述工作的問題,請參閱microsoft/TypeScript#21998

我真的認為 TypeScript 中的枚舉最好被視為完全不透明。 Enum1.Foo"1"而不是"SomethingElseEntirely"這一事實對您的程序的工作應該無關緊要; 如果它確實重要,那么我建議應該放棄enum以支持諸如文字聯合之類的東西。

例如,讓我們看一下Enum1Enum2的以下定義,它們的結構比您的版本多一點,因此實際上我們願意接受一些東西:

enum Enum1 {
    Foo = '1',
    Baz = '2',
}

enum Enum2 {
    Bar = '1',
    Qux = '3'
}

(所以你希望ExclusiveUnion<Enum1, Enum3>Enum1.Baz | Enum2.Qux )。 您可能會對以下定義感到滿意,而不是使用enum

const Enum1 = {
    Foo: '1',
    Baz: '2'
} as const;
type Enum1 = typeof Enum1[keyof typeof Enum1];

const Enum2 = {
    Bar: '1',
    Qux: '3'
} as const;
type Enum2 = typeof Enum2[keyof typeof Enum2];

這行為類似; Enum1Enum2是具有鍵值映射的對象。 類型Enum1Enum2只是相關值類型的聯合:

// type Enum1 = "1" | "2"
// type Enum2 = "1" | "3"

如果您進行了更改,那么您可以使用上面的ExclusiveUnion類型,一切都會按預期工作:

type Enum1XorEnum2 = ExclusiveUnion<Enum1, Enum2>
// type Enum1XorEnum2 = "2" | "3"

const myList: Enum1XorEnum2[] = [
    Enum1.Foo, // error
    Enum1.Baz, // okay
    Enum2.Bar, // error
    Enum2.Qux // okay
]

好的!


如果出於某種原因你真的想繼續使用enum s,你將不得不跳過一些煩人的箍來獲得可用的類型。 你不能只寫

type Nope = ExclusiveUnion<Enum1, Enum2>; // Enum1 | Enum2

因為編譯器認為Enum1 & Enum2 never改變它的想法是非常復雜的:

type Enum1XorEnum2 = Enum1 | Enum2 extends infer T ? (
    T extends string | number ? (
        `${T}` extends `${Enum1}` & `${Enum2}` ? never : T
    ) : never
) : never;
// type Enum1XorEnum2 = Enum1.Baz | Enum2.Qux

這行得通(只要你的enum只是字符串枚舉或只是數字枚舉,但不是兩者的混合),但是 blecch。 它將枚舉值轉換為字符串文字類型,然后檢查Enum1 | Enum2的每個成員。 Enum1 | Enum2並過濾掉在Enum1Enum2的字符串文字版本中都找到的任何成員。

它像以前一樣工作:

const myList: Enum1XorEnum2[] = [
    Enum1.Foo, // error
    Enum1.Baz, // okay
    Enum2.Bar, // error
    Enum2.Qux // okay
]

所以你有 go。 如果您真的打算采用兩個單獨的枚舉並檢查它們的值,則不能將它們視為不透明的,因此我強烈建議您考慮從enum切換到文字聯合。 如您所見,它們更容易處理。

Playground 代碼鏈接

暫無
暫無

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

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