首頁 >web前端 >css教學 >揭開打字稿歧視工會的神秘面紗

揭開打字稿歧視工會的神秘面紗

Joseph Gordon-Levitt
Joseph Gordon-Levitt原創
2025-03-14 10:57:09957瀏覽

Demystifying TypeScript Discriminated Unions

TypeScript是構建可擴展JavaScript應用的強大工具,幾乎已成為大型Web JavaScript項目的標準。然而,對於新手來說,TypeScript也存在一些棘手之處,其中之一就是區分聯合類型。

考慮以下代碼:

 interface Cat {
  weight: number;
  whiskers: number;
}
interface Dog {
  weight: number;
  friendly: boolean;
}
let animal: Dog | Cat;

許多開發者會驚訝地發現,訪問animal時,只有weight屬性是有效的, whiskersfriendly屬性無效。本文將解釋其中的原因。

在深入探討之前,讓我們快速回顧一下結構類型和名義類型,這將有助於理解TypeScript的區分聯合類型。

結構類型

理解結構類型最好的方法是將其與名義類型進行對比。大多數你可能用過的類型化語言都是名義類型化的。例如C#代碼(Java或C 類似):

 class Foo {
  public int x;
}
class Blah {
  public int x;
}

即使FooBlah的結構完全相同,它們也不能相互賦值。以下代碼:

 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必須是DogCat ,但它可能是兩者中的任何一個。如果允許我們訪問friendly屬性,但在實例中animal實際上是Cat而不是Dog ,則可能會導致運行時錯誤。對於whiskers屬性也是如此。

聯合類型是有效值的聯合,而不是屬性的聯合。開發者經常寫這樣的代碼:

 let animal: Dog | Cat;

並期望animal擁有DogCat屬性的聯合。但這又是一個錯誤。這指定animal具有一個值,該值與有效的Dog值和有效的Cat值的聯合匹配。但是TypeScript只允許你訪問它知道存在的屬性。目前,這意味著聯合中所有類型的屬性。

類型收窄

現在,我們有:

 let animal: Dog | Cat;

animalDog時,我們如何正確地將其視為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對像上查看這一點。

區分聯合類型

你可能期望博文到此結束,但不幸的是,通過檢查屬性是否存在來收窄聯合類型非常有限。它對我們簡單的DogCat類型很有效,但是當我們有更多類型以及這些類型之間更多重疊時,事情很容易變得更複雜,也更脆弱。

這就是區分聯合類型派上用場的地方。我們將保留之前的所有內容,只是向每種類型添加一個屬性,其唯一作用是區分(或“區分”)這些類型:

 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屬性的不同值,則此檢查將萬無一失。

唯一的缺點是你現在需要處理一個新屬性。每次創建DogCat的實例時,都必須為ANIMAL_TYPE提供唯一正確的數值。但不用擔心忘記,因為TypeScript會提醒你。 ?

進一步閱讀

如果你想了解更多信息,我建議閱讀TypeScript文檔關於類型收窄的部分。這將更深入地介紹我們這裡討論的內容。在該鏈接中有一個關於類型斷言的部分。這些允許你定義你自己的自定義檢查來收窄類型,而無需使用類型鑑別器,也無需依賴in關鍵字。

結論

在本文開頭,我說過,在以下示例中,為什麼weight是唯一可訪問的屬性:

 interface Cat {
  weight: number;
  whiskers: number;
}
interface Dog {
  weight: number;
  friendly: boolean;
}
let animal: Dog | Cat;

我們學到的是,TypeScript只知道animal可以是DogCat ,但不能同時是兩者。因此,我們只能得到weight ,它是兩者之間唯一的公共屬性。

區分聯合類型的概念是TypeScript區分這些對象的方式,並且這種方式非常具有可擴展性,即使對於更大的對象集合也是如此。因此,我們必須在兩種類型上創建一個新的ANIMAL_TYPE屬性,該屬性保存我們可以用來檢查的單個字面量值。當然,這是另一件需要跟踪的事情,但它也產生了更可靠的結果——這正是我們首先從TypeScript想要得到的。

以上是揭開打字稿歧視工會的神秘面紗的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn