TypeScript Learning

進階類型 (Advanced Types)

⏱️預計 54 分鐘
📖TypeScript 課程
🎯實戰導向

第 9 課:進階類型 (Advanced Types)

學習目標

  • 理解並能夠使用聯合類型嚟表示一個值可以係多種可能性之一
  • 掌握交叉類型,將多個類型合併為一個
  • 學識使用唔同嘅類型守衛 (typeof, instanceof, in, 自訂) 嚟進行類型收窄
  • 理解字面量類型嘅概念同應用場景
  • 掌握處理 nullundefined 嘅技巧,包括可選串連同空值合併
  • 學識使用類型別名簡化複雜類型定義同提高可讀性

1. 聯合類型 (Union Types - |)

聯合類型允許一個變數或參數可以係多個指定類型中嘅任何一個。我哋會用 | (管道符號) 嚟分隔唔同嘅類型。就好似話:「呢個變數可以係數字,又或者可以係字串。」

範例:

typescript
1function printId(id: number | string): void {
2  console.log("你嘅 ID 係: " + id);
3}
4
5printId(101);       // 你嘅 ID 係: 101
6printId("202ABC");  // 你嘅 ID 係: 202ABC
7
8// 處理聯合類型 (類型收窄)
9function processInput(input: string | number | boolean): void {
10  if (typeof input === "string") {
11    // 喺呢個 if 區塊入面,TypeScript 知道 input 肯定係字串
12    console.log("輸入係字串:", input.toUpperCase());
13  } else if (typeof input === "number") {
14    // 喺呢個 else if 區塊入面,TypeScript 知道 input 肯定係數字
15    console.log("輸入係數字:", input.toFixed(2));
16  } else {
17    // 喺呢度,TypeScript 知道 input 肯定係布林值
18    console.log("輸入係布林值:", !input);
19  }
20}
21
22// 聯合類型與陣列
23let mixedArray: (string | number)[] = ["apple", 1, "banana", 2, "orange", 3];
24
25// 函式返回值
26function getLength(value: string | string[]): number {
27  if (typeof value === "string") {
28    return value.length;
29  } else {
30    return value.length; // 陣列都有 length 屬性
31  }
32}

程式碼解釋: 當我哋有一個聯合類型嘅值,我哋通常需要先檢查佢嘅實際類型,然後先可以安全地使用該特定類型嘅方法或屬性,呢個過程就叫做「類型收窄」(Type Narrowing)。

🎯 互動練習 1:聯合類型處理

2. 交叉類型 (Intersection Types - &)

交叉類型允許我哋將多個現有類型合併成一個新類型,呢個新類型將擁有所有原始類型嘅所有成員。我哋會用 & (與符號) 嚟分隔唔同嘅類型。

範例:

typescript
1interface Draggable {
2  drag(): void;
3}
4
5interface Resizable {
6  resize(): void;
7}
8
9// UIElement 類型必須同時實現 Draggable 同 Resizable 介面嘅所有成員
10type UIElement = Draggable & Resizable;
11
12let interactiveBox: UIElement = {
13  drag: () => console.log("拖曳緊個盒..."),
14  resize: () => console.log("調整緊個盒嘅大細...")
15};
16
17// 合併物件屬性
18interface Person {
19  name: string;
20  age: number;
21}
22
23interface Employee {
24  employeeId: string;
25  department: string;
26}
27
28// EmployedPerson 類型將會擁有 Person 同 Employee 嘅所有屬性
29type EmployedPerson = Person & Employee;
30
31const john: EmployedPerson = {
32  name: "John Doe",
33  age: 30,
34  employeeId: "E123",
35  department: "工程部"
36};

🎯 互動練習 2:交叉類型組合

3. 類型守衛 (Type Guards)

類型守衛係一種喺執行時期檢查變數類型嘅表達式。當 TypeScript 編譯器遇到類型守衛,並且守衛條件為真嘅時候,佢可以喺嗰個條件區塊內部將變數嘅類型「收窄」到一個更具體嘅類型。

3.1 typeof 類型守衛

typeof 運算子可以檢查一個變數嘅基本類型。

範例:

typescript
1function padLeft(value: string, padding: string | number) {
2  if (typeof padding === "number") {
3    // 喺呢個區塊,TypeScript 知道 padding 係 number
4    return Array(padding + 1).join(" ") + value;
5  }
6  if (typeof padding === "string") {
7    // 喺呢個區塊,TypeScript 知道 padding 係 string
8    return padding + value;
9  }
10  throw new Error(`預期係字串或數字,但收到 '${typeof padding}'.`);
11}

3.2 instanceof 類型守衛

instanceof 運算子用於檢查一個物件是否係某個類別嘅實例。

範例:

typescript
1class Bird {
2  fly() {
3    console.log("雀仔飛緊...");
4  }
5  layEggs() {
6    console.log("雀仔生緊蛋...");
7  }
8}
9
10class Fish {
11  swim() {
12    console.log("魚游緊水...");
13  }
14  layEggs() {
15    console.log("魚生緊蛋...");
16  }
17}
18
19function getPetMove(pet: Bird | Fish) {
20  pet.layEggs(); // 兩個類別都有 layEggs 方法
21
22  if (pet instanceof Bird) {
23    // 喺呢個區塊,TypeScript 知道 pet 係 Bird 嘅實例
24    pet.fly();
25  } else {
26    // 喺呢個區塊,TypeScript 知道 pet 係 Fish 嘅實例
27    pet.swim();
28  }
29}

3.3 in 類型守衛

in 運算子用於檢查一個物件是否有特定嘅屬性。

範例:

typescript
1interface Car {
2  drive(): void;
3  wheels: number;
4}
5
6interface Boat {
7  sail(): void;
8  propeller: boolean;
9}
10
11function operateVehicle(vehicle: Car | Boat) {
12  if ("wheels" in vehicle) {
13    // 喺呢個區塊,TypeScript 知道 vehicle 係 Car
14    vehicle.drive();
15    console.log(`車有 ${vehicle.wheels} 個輪`);
16  } else {
17    // 喺呢個區塊,TypeScript 知道 vehicle 係 Boat
18    vehicle.sail();
19    console.log(`船有螺旋槳: ${vehicle.propeller}`);
20  }
21}

🎯 互動練習 3:類型守衛實作

4. 字面量類型 (Literal Types)

字面量類型允許我哋指定一個變數只能係特定嘅值,而唔係整個類型嘅任何值。

範例:

typescript
1// 字串字面量類型
2type Direction = "up" | "down" | "left" | "right";
3
4function move(direction: Direction) {
5  console.log(`移動方向: ${direction}`);
6}
7
8move("up");    // OK
9move("down");  // OK
10// move("diagonal"); // 錯誤:不是允許嘅值
11
12// 數字字面量類型
13type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
14
15function rollDice(): DiceRoll {
16  return Math.floor(Math.random() * 6) + 1 as DiceRoll;
17}
18
19// 布林字面量類型
20type Success = true;
21type Failure = false;
22
23// 混合字面量類型
24type Status = "loading" | "success" | "error" | 404 | 500;
25
26function handleStatus(status: Status) {
27  switch (status) {
28    case "loading":
29      console.log("載入中...");
30      break;
31    case "success":
32      console.log("成功!");
33      break;
34    case "error":
35      console.log("發生錯誤");
36      break;
37    case 404:
38      console.log("找不到頁面");
39      break;
40    case 500:
41      console.log("伺服器錯誤");
42      break;
43  }
44}

🎯 互動練習 4:字面量類型應用

5. 處理 nullundefined

TypeScript 提供咗多種方式嚟安全地處理可能為空嘅值。

5.1 可選串連 (Optional Chaining - ?.)

typescript
1interface User {
2  name: string;
3  address?: {
4    street: string;
5    city: string;
6  };
7}
8
9function getUserCity(user: User): string | undefined {
10  // 安全地存取嵌套屬性
11  return user.address?.city;
12}
13
14// 可選方法調用
15interface API {
16  getData?(): string;
17}
18
19function callAPI(api: API) {
20  // 只有當 getData 方法存在時先調用
21  const data = api.getData?.();
22  return data;
23}

5.2 空值合併 (Nullish Coalescing - ??)

typescript
1function getDisplayName(name: string | null | undefined): string {
2  // 只有當 name 係 null 或 undefined 時先使用預設值
3  return name ?? "匿名用戶";
4}
5
6// 與 || 嘅區別
7function example(value: string | null | undefined | "" | 0 | false) {
8  console.log(value || "預設值");  // 所有 falsy 值都會使用預設值
9  console.log(value ?? "預設值");  // 只有 null/undefined 會使用預設值
10}

🎯 互動練習 5:安全處理空值

6. 類型別名 (Type Aliases)

類型別名允許我哋為複雜嘅類型創建一個簡短嘅名稱,提高程式碼嘅可讀性同可維護性。

範例:

typescript
1// 基本類型別名
2type ID = string | number;
3type UserRole = "admin" | "user" | "guest";
4
5// 複雜物件類型別名
6type User = {
7  id: ID;
8  name: string;
9  role: UserRole;
10  isActive: boolean;
11};
12
13// 函式類型別名
14type EventHandler = (event: string) => void;
15type Validator<T> = (value: T) => boolean;
16
17// 條件類型別名
18type NonNullable<T> = T extends null | undefined ? never : T;
19
20// 使用類型別名
21function createUser(id: ID, name: string, role: UserRole): User {
22  return {
23    id,
24    name,
25    role,
26    isActive: true
27  };
28}
29
30const stringValidator: Validator<string> = (value) => value.length > 0;
31const numberValidator: Validator<number> = (value) => value > 0;

🎯 互動練習 6:綜合練習 - 電商系統

總結

今日學到嘅重點

  • 聯合類型 (|):表示一個值可以係多種類型之一
  • 交叉類型 (&):將多個類型合併為一個新類型
  • 類型守衛:使用 typeofinstanceofin 進行類型收窄
  • 字面量類型:限制變數只能係特定嘅值
  • 空值處理:使用 ?.?? 安全處理 null/undefined
  • 類型別名:為複雜類型創建簡短嘅名稱

課後建議

  1. 練習使用聯合類型同交叉類型組合複雜嘅數據結構
  2. 熟練掌握各種類型守衛嘅使用場景
  3. 在實際專案中使用字面量類型提高程式碼安全性
  4. 善用可選串連同空值合併處理不確定嘅數據

下一課預告

下一課我哋會學習:

  • 列舉 (Enums) 嘅使用
  • 模組系統嘅基礎
  • 匯入同匯出嘅語法
  • 命名空間嘅概念

恭喜你完成第九課! 🎉

進階類型係 TypeScript 嘅核心功能,掌握呢啲概念可以讓你寫出更安全、更靈活嘅程式碼。繼續練習,你會發現呢啲類型工具喺實際開發中嘅強大威力!