TypeScript 型別保護

admin發表於2019-04-27

TypeScript 聯合型別一章節介紹過,如果一個值是聯合型別,那麼只能訪問聯合型別的共有成員。

程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
class Bird{
  leg=2;
  color="white";
  fly(){
    // code
  }
}
class Insect {
  leg=8;
  color="black";
  eat(){
    // code
  }
}
function antzone(): Bird | Insect {
    return new Bird();
}
let ant = antzone();
ant.color;
ant.leg;
ant.eat()// 報錯,只能訪問共有成員

antzone()函式返回值是聯合型別,也就是既有可能是Bird型別,也有可能是Insect型別。

有時候我們想確切的知道這個返回值到底是屬於哪個型別,在原生JavaScript中,判斷這個非常簡單。

只要判斷物件是否具有對應屬性即可,但是在TypeScript可能並不適用,看如下程式碼例項:

[JavaScript] 純文字檢視 複製程式碼
let ant= antzone();
if(ant.fly){
  ant.fly();
}else if(ant.eat){
  ant.fly();
}

上面的程式碼會報錯,因為只能訪問聯合型別的共有成員;程式碼修改如下:

[typescript] 純文字檢視 複製程式碼
let ant= antzone();
if((<Bird>ant).fly){
  (<Bird>ant).fly();
}else if((<Insect>ant).eat){
  (<Insect>ant).eat();
}

雖然使用斷言解決此問題,但比較繁瑣,if語句中使用了斷言,語句塊中還是要斷言。

較好的解決方案是,只要判斷成功,後面對應作用域中的型別就確定。

TypeScript型別保護機制提供了這樣的功能,下面分別做一下介紹。

一.自定義的型別保護:

型別保護是一些表示式,它們會檢查以確保在某域裡的型別。

要定義一個型別保護,只要定義一個函式,它的返回值是一個型別謂詞。

程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
function isBird(ant: Bird | Insect): ant is Bird {
  return (<Bird>ant).fly !== undefined;
}

"ant is Bird"是一個型別謂詞,格式如下:

[typescript] 純文字檢視 複製程式碼
parameterName is Type

parameterName必須是當前函式簽名裡的一個引數名。

如果函式返回值為true,那麼也就意味著型別謂詞是成立的,於是它後面作用域的型別也就被固定為Type。

程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
let ant=antzone();
function isBird(ant: Bird | Insect): ant is Bird {
  return (<Bird>ant).fly !== undefined;
}
if (isBird(ant)) {
  ant.fly();
}
else {
  ant.eat();
}

只要isBird(ant)成立,那麼它後面作用域中的型別就確定為Bird。

二.typeof型別保護:

在TypeScript中,typeof在判斷資料型別的同時,直接提供了型別保護功能。

程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
function ant(param: string | number) {
  if (typeof param === "number") {
    console.log(param+5);
  }
  if (typeof param === "string") {
    console.log(param+"螞蟻部落");
  }
}

typeof實現了型別保護及功能,截圖如下:

a:3:{s:3:\"pic\";s:43:\"portal/201904/27/082817m8t4p88tots7cktg.jpg\";s:5:\"thumb\";s:0:\"\";s:6:\"remote\";N;}

原本是聯合型別,由於應用了typeof,後面作用域的param就確定為number型別。

typeof型別保護只有兩種形式能被識別:typeof v === "typename"和typeof v !== "typename"。

"typename"必須是"number","string","boolean"或"symbol"。 

但是TypeScript並不會阻止與其它字串比較,語言不會把那些表示式識別為型別保護。

三.instanceof型別保護:

instanceof的使用方式和typeof非常的類似,程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
interface Padder {
  getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) { }
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}
class StringPadder implements Padder {
  constructor(private value: string) { }
  getPaddingString() {
    return this.value;
  }
}
 
function getRandomPadder() {
  return Math.random() < 0.5 ?
    new SpaceRepeatingPadder(4) :
    new StringPadder("  ");
}
 
// 型別為SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
 
if (padder instanceof SpaceRepeatingPadder) {
  padder; // 型別細化為'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
  padder; // 型別細化為'StringPadder'
}

四.switch case型別保護:

通過switch case來實現型別保護,原理都是一樣的。

程式碼例項如下:

[typescript] 純文字檢視 複製程式碼
interface Square {
  kind: "square";
  size: number;
}
interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}
interface Circle {
  kind: "circle";
  radius: number;
}
type Shape = Square | Rectangle | Circle;
 
function area(shape: Shape) {
  switch (shape.kind) {
    case "square": return shape.size * shape.size;
    case "rectangle": return shape.height * shape.width;
    case "circle": return Math.PI * shape.radius ** 2;
  }
}

特別說明:型別保護並不僅限於以上幾種,大致能夠實現型別識別,即可實現型別保護。

相關文章