TypeScript類型守衛、聯合類型、交叉類型


一、類型守衛

  類型保護是可執行運行時檢查的一種表達式,用於確保該類型在一定的范圍內。 換句話說,類型保護可以保證一個字符串是一個字符串,盡管它的值也可以是一個數值。類型保護與特性檢測並不是完全不同,其主要思想是嘗試檢測屬性、方法或原型,以確定如何處理值。目前主要有四種的方式來實現類型保護:

1、in 關鍵字:從下面代碼我門可以看出 可以判斷不同的屬性進行不同的業務。
interface Admin { name: string; privileges: string[]; } interface Employee { name: string; startDate: Date; } type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }

2、typeof 關鍵字

function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
  typeof 類型保護只支持兩種形式:typeof v === "typename"typeof v !== typename"typename" 必須是 "number""string""boolean""symbol"。 但是 TypeScript 並不會阻止你與其它字符串比較,語言不會把那些表達式識別為類型保護。

3、instanceof 關鍵字

4、自定義類型保護的類型謂詞

  總結:簡言之,類型守衛就是加一層判斷,對於不同的判斷進行不同的業務處理。

二、聯合類型

1、聯合類型:聯合類型(Union Types)可以通過管道 ( | ) 將變量設置多種類型,賦值時可以根據設置的類型來賦值。

  注意:只能賦值指定的類型,如果賦值其它類型就會報錯。

  創建聯合類型的語法格式如下:Type1|Type2|Type3

  比如:可以用 | 來支持多種類型

let x: number | null | undefined; x = 1; // 運行正確 x = undefined; // 運行正確 x = null; // 運行正確
  聯合類型通常與 nullundefined 一起使用。例如,這里 name 的類型是 string | undefined 意味着可以將 stringundefined 的值傳遞給sayHello 函數。
const sayHello = (name: string | undefined) => {}; sayHello("***"); sayHello(undefined);

  通過這個示例我們可以知道類型 A 和類型 B 聯合后的類型是同時接受 A 和 B 值的類型。此外,對於聯合類型來說,你可能會遇到以下的用法:

let num: 1 | 2 = 1; type EventNames = 'click' | 'scroll' | 'mousemove';

  以上示例中的 12'click' 被稱為字面量類型,用來約束取值只能是某幾個值中的一個

2、可辨識聯合

  TypeScript 可辨識聯合(Discriminated Unions)類型,也稱為代數數據類型或標簽聯合類型。它包含 3 個要點:可辨識、聯合類型和類型守衛。

  這種類型的本質是結合聯合類型和字面量類型的一種類型保護方法。如果一個類型是多個類型的聯合類型,且多個類型含有一個公共屬性,那么就可以利用這個公共屬性,來創建不同的類型保護區塊。

(1)可辨識:可辨識要求聯合類型中的每個元素都含有一個單例類型屬性,比如:
enum CarTransmission { Automatic = 200, Manual = 300 } interface Motorcycle { vType: "motorcycle"; // discriminant
  make: number; // year
} interface Car { vType: "car"; // discriminant
 transmission: CarTransmission } interface Truck { vType: "truck"; // discriminant
  capacity: number; // in tons
}

  在上述代碼中,我們分別定義了 MotorcycleCarTruck 三個接口,在這些接口中都包含一個 vType 屬性,該屬性被稱為可辨識的屬性,而其它的屬性只跟特性的接口相關。

(2)聯合類型:基於前面定義了三個接口,我們可以創建一個 Vehicle 聯合類型:

type Vehicle = Motorcycle | Car | Truck;

  現在我們就可以開始使用 Vehicle 聯合類型,對於 Vehicle 類型的變量,它可以表示不同類型的車輛。

(3)類型守衛:下面我們來定義一個 evaluatePrice 方法,該方法用於根據車輛的類型、容量和評估因子來計算價格,具體實現如下:

const EVALUATION_FACTOR = Math.PI; function evaluatePrice(vehicle: Vehicle) { return vehicle.capacity * EVALUATION_FACTOR; } const myTruck: Truck = { vType: "truck", capacity: 9.5 }; evaluatePrice(myTruck); // 對於以上代碼,TypeScript 編譯器將會提示以下錯誤信息:
Property 'capacity' does not exist on type 'Vehicle'. Property 'capacity' does not exist on type 'Motorcycle'.
  原因是在 Motorcycle 接口中,並不存在 capacity 屬性,而對於 Car 接口來說,它也不存在 capacity 屬性。那么,現在我們應該如何解決以上問題呢?這時,我們可以使用類型守衛。下面我們來重構一下前面定義的 evaluatePrice 方法,重構后的代碼如下:
function evaluatePrice(vehicle: Vehicle) { switch(vehicle.vType) { case "car": return vehicle.transmission * EVALUATION_FACTOR; case "truck": return vehicle.capacity * EVALUATION_FACTOR; case "motorcycle": return vehicle.make * EVALUATION_FACTOR; } }

  在以上代碼中,我們使用 switchcase 運算符來實現類型守衛,從而確保在 evaluatePrice 方法中,我們可以安全地訪問 vehicle 對象中的所包含的屬性,來正確的計算該車輛類型所對應的價格。

  所以從這里也可以看出所謂類型守衛就是確保所使用的類型均可以正常使用,從而針對不同類型,使用不同的業務處理。

三、交叉類型

  在 TypeScript 中交叉類型是將多個類型合並為一個類型。通過 & 運算符可以將現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。

type PartialPointX = { x: number; }; type Point = PartialPointX & { y: number; }; let point: Point = { x: 1, y: 1 }

  在上面代碼中我們先定義了 PartialPointX 類型,接着使用 & 運算符創建一個新的 Point 類型,表示一個含有 x 和 y 坐標的點,然后定義了一個 Point 類型的變量並初始化。

1、同名基礎類型屬性的合並

  那么問題來了,假設在合並多個類型的過程中,剛好出現某些類型存在相同的成員,但對應的類型又不一致,比如:

interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string
}

type XY = X & Y;
type YX = Y & X;

  在上面的代碼中,接口 X 和接口 Y 都含有一個相同的成員 c,但它們的類型不一致。對於這種情況,此時 XY 類型或 YX 類型中成員 c 的類型是不是可以是 stringnumber 類型呢?不是的,結論是 never。

  為什么接口 X 和接口 Y 混入后,成員 c 的類型會變成 never 呢?這是因為混入后成員 c 的類型為 string & number,即成員 c 的類型既可以是 string 類型又可以是 number 類型。很明顯這種類型是不存在的,所以混入后成員 c 的類型為 never

2、同名非基礎類型屬性的合並

  在上面示例中,剛好接口 X 和接口 Y 中內部成員 c 的類型都是基本數據類型,那么如果是非基本數據類型的話,又會是什么情形。我們來看個具體的例子:

interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: '***',
    f: 666
  }
};
console.log('abc:', abc);

  以上代碼可以成功運行,且 abc 就為上述結果。由此可知:在混入多個類型時,若存在相同的成員,且成員類型為非基本數據類型,那么是可以成功合並。

  總結:

(1)同名基礎類型屬性合並:string & number,這樣的值不存在,所以是 never;

(2)同名非基礎類型屬性合並:比如 { a: string } & { b: number },可以成功合並為 { a: string, b: number }。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM