[英]Typescript check array value is in enum1 or enum 2 but not both
是否可以檢查myList
中的值是否來自Enum1
或Enum2
但不能同時來自兩者?
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
類型的所有值,包括A
和B
類型的任何值。
看起來您正在嘗試表示排他性聯合,它專門排除了交集。 就像是
const myList: ExclusiveUnion<Enum1, Enum2>[] = [ /* ... */ ];
不幸的是,您不能寫出適用於所有類型T
和U
的完全通用的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);
但它沒有,所以你不能。
但是:對於T
和U
本身是文字類型聯合的特殊情況(如"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>
因為Enum1
和Enum2
類型都應該類似於文字字符串/數字類型的聯合。 這在概念上是可行的,但在實踐中卻並非如此。 問題是枚舉很奇怪。
TypeScript 中的枚舉值有一些相當奇怪的行為; 有關阻止上述工作的問題,請參閱microsoft/TypeScript#21998 。
我真的認為 TypeScript 中的枚舉最好被視為完全不透明。 Enum1.Foo
是"1"
而不是"SomethingElseEntirely"
這一事實對您的程序的工作應該無關緊要; 如果它確實重要,那么我建議應該放棄enum
以支持諸如文字聯合之類的東西。
例如,讓我們看一下Enum1
和Enum2
的以下定義,它們的結構比您的版本多一點,因此實際上我們願意接受一些東西:
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];
這行為類似; 值Enum1
和Enum2
是具有鍵值映射的對象。 類型Enum1
和Enum2
只是相關值類型的聯合:
// 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
並過濾掉在Enum1
和Enum2
的字符串文字版本中都找到的任何成員。
它像以前一樣工作:
const myList: Enum1XorEnum2[] = [
Enum1.Foo, // error
Enum1.Baz, // okay
Enum2.Bar, // error
Enum2.Qux // okay
]
所以你有 go。 如果您真的打算采用兩個單獨的枚舉並檢查它們的值,則不能將它們視為不透明的,因此我強烈建議您考慮從enum
切換到文字聯合。 如您所見,它們更容易處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.