TypeScript是構建可擴展JavaScript應用的強大工具,幾乎已成為大型Web JavaScript項目的標準。然而,對於新手來說,TypeScript也存在一些棘手之處,其中之一就是區分聯合類型。
考慮以下代碼:
interface Cat { weight: number; whiskers: number; } interface Dog { weight: number; friendly: boolean; } let animal: Dog | Cat;
許多開發者會驚訝地發現,訪問animal
時,只有weight
屬性是有效的, whiskers
和friendly
屬性無效。本文將解釋其中的原因。
在深入探討之前,讓我們快速回顧一下結構類型和名義類型,這將有助於理解TypeScript的區分聯合類型。
理解結構類型最好的方法是將其與名義類型進行對比。大多數你可能用過的類型化語言都是名義類型化的。例如C#代碼(Java或C 類似):
class Foo { public int x; } class Blah { public int x; }
即使Foo
和Blah
的結構完全相同,它們也不能相互賦值。以下代碼:
Blah b = new Foo();
會產生以下錯誤:
<code>无法隐式转换类型“Foo”为“Blah”</code>
這些類的結構無關緊要。 Foo
類型的變量只能賦值給Foo
類的實例(或其子類)。
TypeScript則相反,它採用結構類型。如果兩個類型具有相同的結構,TypeScript就認為它們是兼容的。
因此,以下代碼可以正常運行:
class Foo { x: number = 0; } class Blah { x: number = 0; } let f: Foo = new Blah(); let b: Blah = new Foo();
讓我們進一步說明這一點。給定以下代碼:
class Foo { x: number = 0; } let f: Foo;
f
是一個變量,它可以保存任何與Foo
類實例結構相同的對象,在本例中,這意味著一個表示數字的x
屬性。這意味著即使是一個普通的JavaScript對像也可以被接受。
let f: Foo; f = { x: 0 };
讓我們回到本文開頭的代碼:
interface Cat { weight: number; whiskers: number; } interface Dog { weight: number; friendly: boolean; }
我們知道:
let animal: Dog;
使animal
成為任何與Dog
接口結構相同的對象。那麼以下代碼是什麼意思?
let animal: Dog | Cat;
這將animal
類型定義為任何與Dog
接口匹配的對象,或者任何與Cat
接口匹配的對象。
那麼為什麼現在的animal
只允許我們訪問weight
屬性呢?簡單來說,是因為TypeScript不知道它是什麼類型。 TypeScript知道animal
必須是Dog
或Cat
,但它可能是兩者中的任何一個。如果允許我們訪問friendly
屬性,但在實例中animal
實際上是Cat
而不是Dog
,則可能會導致運行時錯誤。對於whiskers
屬性也是如此。
聯合類型是有效值的聯合,而不是屬性的聯合。開發者經常寫這樣的代碼:
let animal: Dog | Cat;
並期望animal
擁有Dog
和Cat
屬性的聯合。但這又是一個錯誤。這指定animal
具有一個值,該值與有效的Dog
值和有效的Cat
值的聯合匹配。但是TypeScript只允許你訪問它知道存在的屬性。目前,這意味著聯合中所有類型的屬性。
現在,我們有:
let animal: Dog | Cat;
當animal
是Dog
時,我們如何正確地將其視為Dog
並訪問Dog
接口上的屬性,當它是Cat
時也同樣處理?目前,我們可以使用in
運算符。這是一個你可能不太常看到的舊式JavaScript運算符,它允許我們測試一個屬性是否存在於一個對像中。例如:
let o = { a: 12 }; "a" in o; // true "x" in o; // false
事實證明,TypeScript與in
運算符深度集成。讓我們看看如何使用:
let animal: Dog | Cat = {} as any; if ("friendly" in animal) { console.log(animal.friendly); } else { console.log(animal.whiskers); }
這段代碼不會產生錯誤。在if
塊內,TypeScript知道存在friendly
屬性,因此將animal
轉換為Dog
。在else
塊內,TypeScript類似地將animal
視為Cat
。你甚至可以在代碼編輯器中將鼠標懸停在這些塊中的animal
對像上查看這一點。
你可能期望博文到此結束,但不幸的是,通過檢查屬性是否存在來收窄聯合類型非常有限。它對我們簡單的Dog
和Cat
類型很有效,但是當我們有更多類型以及這些類型之間更多重疊時,事情很容易變得更複雜,也更脆弱。
這就是區分聯合類型派上用場的地方。我們將保留之前的所有內容,只是向每種類型添加一個屬性,其唯一作用是區分(或“區分”)這些類型:
interface Cat { weight: number; whiskers: number; ANIMAL_TYPE: "CAT"; } interface Dog { weight: number; friendly: boolean; ANIMAL_TYPE: "DOG"; }
請注意兩種類型上的ANIMAL_TYPE
屬性。不要將其誤認為是具有兩個不同值的字符串;這是一個字面量類型。 ANIMAL_TYPE: "CAT";
表示一個恰好包含字符串“CAT”的類型,僅此而已。
現在我們的檢查變得更可靠:
let animal: Dog | Cat = {} as any; if (animal.ANIMAL_TYPE === "DOG") { console.log(animal.friendly); } else { console.log(animal.whiskers); }
假設聯合中參與的每個類型都具有ANIMAL_TYPE
屬性的不同值,則此檢查將萬無一失。
唯一的缺點是你現在需要處理一個新屬性。每次創建Dog
或Cat
的實例時,都必須為ANIMAL_TYPE
提供唯一正確的數值。但不用擔心忘記,因為TypeScript會提醒你。 ?
如果你想了解更多信息,我建議閱讀TypeScript文檔關於類型收窄的部分。這將更深入地介紹我們這裡討論的內容。在該鏈接中有一個關於類型斷言的部分。這些允許你定義你自己的自定義檢查來收窄類型,而無需使用類型鑑別器,也無需依賴in
關鍵字。
在本文開頭,我說過,在以下示例中,為什麼weight
是唯一可訪問的屬性:
interface Cat { weight: number; whiskers: number; } interface Dog { weight: number; friendly: boolean; } let animal: Dog | Cat;
我們學到的是,TypeScript只知道animal
可以是Dog
或Cat
,但不能同時是兩者。因此,我們只能得到weight
,它是兩者之間唯一的公共屬性。
區分聯合類型的概念是TypeScript區分這些對象的方式,並且這種方式非常具有可擴展性,即使對於更大的對象集合也是如此。因此,我們必須在兩種類型上創建一個新的ANIMAL_TYPE
屬性,該屬性保存我們可以用來檢查的單個字面量值。當然,這是另一件需要跟踪的事情,但它也產生了更可靠的結果——這正是我們首先從TypeScript想要得到的。
以上是揭開打字稿歧視工會的神秘面紗的詳細內容。更多資訊請關注PHP中文網其他相關文章!