iT邦幫忙

2024 iThome 鐵人賽

DAY 12
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 12

TypeScript 初學者也能看的學習指南 12 - 聯合型別 ( | ) 、交集型別 ( & )

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240922/20149362qp7WN20XzT.png

本篇要來講解「聯合型別」和「交集型別」這兩親戚
讓大家了解兩者的差別、使用時機,以及使用上該注意的地方

聯合型別(Union Type)

https://ithelp.ithome.com.tw/upload/images/20240922/20149362U7I2CttLSK.png
請原諒我的圖真的畫得很醜XD

在前面文章的範例中,聯合型別已經不時的出場很多次,那今天就來做更多的介紹

使用時機:當你想定義兩個以上(含)的型別時,會用 | 來隔開各個型別

範例

let id: string | number; // 聯合型別
id = "12345";
id = 333;
id = true; // ❌ Type 'boolean' is not assignable to type 'string | number'.

function displayPrice(price: number | [number, number]) {  // 聯合型別
    if (typeof price === "number") {
        console.log(`價格:${price}`);
    } else {
        console.log(`價格範圍:${price[0]}~${price[1]}`);
    }
};

displayPrice(50);
displayPrice([33, 55]);

id 的 type 只能是 stringnumber
給予這兩個型別外的值就會報錯
而函式的參數也可以是聯合型別

聯合型別的使用

function printId(id: number | string) {
  console.log(id.toUpperCase());
};

這個範例會得到一個錯誤結果,因為 id 若今天傳入的是 number 就無法使用字串的方法 toUpperCase()
https://ithelp.ithome.com.tw/upload/images/20240922/2014936273q21Lu3WB.png

解法是可以透過 narrow 的概念來「限縮程式碼的聯合範圍」
那要如何限縮範圍呢?可以透過 typeof , Array.isArray(), instanceof 等任何方式來判斷具體型別,並經常搭配 if...else... 的使用,像是下方的例子

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
};

想看更多很推薦這篇鐵人文章,寫得很清楚 👍🏻

keyof typeof 關鍵字

不只是透過明確的使用 | 定義聯合型別
這篇文章enums at compile time 中有提到在「編譯時」可以透過 keyof typeof 去取得 enum 中的所有 key,並轉換成 string Union Type (字串聯合型別),

enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG,
}

type levelKey = keyof typeof LogLevel; // type levelKey = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';

交集型別(Intersection Types)

https://ithelp.ithome.com.tw/upload/images/20240922/20149362V2vt2feptz.png

使用時機:當一個值要同時符合所有給定的型別時,可以使用交集型別,用 & 來組合現有的物件型別(Object Types)

範例

  1. ❌ 錯誤示範:無法同時定義兩個 原始型別
    這個範例的結果是錯誤的,因為 id 不可能同時是 stringnumber,會被推斷為 never,表示該屬性沒有可接受的值
function printId(id: string & number) {
  console.log(id);
};

printId('123'); // ❌ Argument of type 'string' is not assignable to parameter of type 'never'.
printId(123); // ❌ 訊息同上
  1. ✅ 正確示範
    type EmployeePersonPersonEmployee 的交集,也就是說 EmployeePerson 要同時具有 PersonEmployee 的屬性
type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: number;
  department: string;
};

type EmployeePerson = Person & Employee; // 交集型別

let employee: EmployeePerson = {  // ✅ Pass
  name: "John",
  age: 30,
  employeeId: 12345,
  department: "Engineering"
};
  1. TypeScript 的貼心提示
    接續「範例 2」的內容,department 屬性故意少打最後一個「t」
// ... 前面省略
let employee: EmployeePerson = { 
  name: "John",
  age: 30,
  employeeId: 12345,
  departmen: "Engineering" // department 故意少打最後一個「t」
};

TypeScript 會很貼心地提醒你是不是打錯字🥳
https://ithelp.ithome.com.tw/upload/images/20240922/20149362uyKVg3FFDE.png

  1. ❌ 錯誤示範:如果在不同型別中有同名的屬性時
    在這種情況下,valueA 中是 number,在 B 中是 string,無法同時是 number 又是 string,跟「範例 1」是一樣的,會被推斷為 never

TypeScript 會嘗試將「交集型別」的屬性合併。如果屬性兼容,則會成功合併;如果不兼容( = 同一個屬性名稱有不同的型別),則 TypeScript 會報錯!

type A = {
  value: number; // 屬性相同
};

type B = {
  value: string;  // 屬性相同
};

type C = A & B;

let test: C = { value: 123 };  // ❌ Type 'number' is not assignable to type 'never'.

Do's & Don't

❌ 不要在 overloads(超載)上,分別對「同一個參數」定義多個型別

interface Moment {
  utcOffset(): number;
  utcOffset(b: number): Moment;
  utcOffset(b: string): Moment;
}

✅ 可以使用「聯合型別」

interface Moment {
  utcOffset(): number;
  utcOffset(b: number | string): Moment;
}

每天講的內容有推到 github 上喔

References


上一篇
TypeScript 初學者也能看的學習指南 11 - 常數列舉(const Enums) & 外部列舉(Ambient Enums)
下一篇
TypeScript 初學者也能看的學習指南 13 - Literal Types 明文型別/字面量(值)型別
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言