TypeScript - Union Types

學習資源:

Codecademy

介紹

TypeScript 讓我們針對變數給予不同的型別。而通常若給予變數一個型別的話,我們就只允許給變數這個型別的值。

而另一方面,也可以給變數 any 型別,所以在 assign 值的時候就相對自由。

而有時候我們有可能會需要同時使用兩者。比方說,我們必須編寫一個程式來獲取員工的 ID,然後將 ID 印出到控制檯。問題是,員工的 ID 可以是字串或數字。 由於我們需要 ID 變數允許多個型別,因此我們可以使用 any 型別,例如:

let ID: any;
console.log(`The ID is ${ID}.`);

然而若使用 any 型別的話,TypeScript 就不會起到監督型別的作用。所以為了讓型別的註釋更彈性一些,才有了 Union 這個方式

Defining Unions Types 定義聯合型別

Union 可以讓我們定義多個型別,以下是以上面的例子重新定義的樣子:

let ID: string | number;

// number
ID = 1;

// or string
ID = '001';

console.log(`The ID is ${ID}.`);

以上的例子可以看到可以看到ID 到型別可以允許字串跟數字。這樣的方式比較單一型別更為彈性,並且也比 any 更為嚴謹。

Union 也可以使用在函式的參數標記裡。這樣的方式很方便,因為函式常常會需要這種多元的 input 。

function getMarginLeft(margin: string | number) {
  return { 'marginLeft': margin };
}

Type Narrowing

有了 Union 後,雖然型別註釋上更為彈性,但也還有一些事需要考慮。

function getMarginLeft(margin: string | number) {
  // ...
}

比如上面的函式,他可以接受字串或是數字,所以這部分可能要針對這兩個型別做不同的邏輯處理。可以像下面的例子來做這樣的型別確認:

function getMarginLeft(margin: string | number) {
  // margin may be a string or number here

  if (typeof margin === 'string') {
    // margin must be a string here
  }
}

可以藉由上面的判斷式,來確認參數的型別,再做後續的處理,比如:

if (typeof margin === 'string') {
  return margin.toLowerCase();
}

而這樣的概念就叫做 Type Narrowing,藉由使用 union 還有型別的邏輯判斷,讓我們在程式碼中縮小變數的類型範圍。

Inferred Union Return Types

TypeScript 一個好用的地方是,它能夠在許多情況下推斷型別,這樣我們就不必手動去編寫它們。一個很好的例子是函式的返回型別。TypeScript 會檢視函式的內容,並推斷該函式可能會返回哪些型別。如果有多個可能的返回型別,TypeScript 將推斷返回型別為 union。

以下面的例子為例,呼叫一個會失敗的函式 getBookFromServer()

function getBook() {
  try {
    return getBookFromServer();
  } catch (error) {
    return `Something went wrong: ${error}`;
  }
}

若這個函式呼叫成功,他會返回一個 Book 型別。若失敗,會返回 string。而 TypeScript 可以藉由 getBook() 可以返回 Book 或 string 型別來做型別推斷,讓我們不用手動去標記。

Unions and Arrays

接下來會介紹當 Union 以及 Array 結合時優勢。

比如說,可以在 TypeScript 中用數字或字串型別表示時間。所以如果有兩種型別的日期列表,就需要一個允許字串和數字值的陣列。Unions 在這裡就會很有幫助。

const dateNumber = new Date().getTime(); // returns a number
const dateString = new Date().toString(); // returns a string

const timesList: (string | number)[] = [dateNumber, dateString];

以上面的例子,timesList 變數允許字串和數字型別作為其陣列中的值。所以如果我們嘗試向 timesList 新增一個不是 any 型別的值,比如 timesList.push(true) ,TypeScript 就會顯示一個錯誤。

Common Key Value Pairs

當使用 Union 方法時,TypeScript 只會允許使用兩個型別都可以共用的方法,比如:

const batteryStatus: boolean | number = false;

batteryStatus.toString(); // No TypeScript error
batteryStatus.toFixed(2); // TypeScript error

因為 toFixed() 只能給 number 使用,所以會出現錯誤。

而這樣的規則也同樣適用於 object。

type Goose = { 
  isPettable: boolean; 
  hasFeathers: boolean;
  canThwartAPicnic: boolean;
}

type Moose = {
  isPettable: boolean; 
  hasHoofs: boolean;
}

const pettingZooAnimal: Goose | Moose = { isPettable: true };

console.log(pettingZooAnimal.isPettable); // No TypeScript error
console.log(pettingZooAnimal.hasHoofs); // TypeScript error

Unions with Literal Types

我們也可以使用字面的型別來使用 Unions,讓我們需要針對狀態做定義的時候。

比如以下的例子,若我們想控制紅綠燈的狀態時:

type Color = 'green' | 'yellow' | 'red';

function changeLight(color: Color) {
  // ...
}

這邊定義 changeLight() 接收的參數只能是紅綠燈的三個狀態,若丟入其他顏色就會產生錯誤,藉此做到狀態上的管控。並且也讓寫函式的時候可以減少一些意外地狀況。