TypeScript Learning

TypeScript 基礎類型

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

第 3 課:TypeScript 基礎類型

學習目標

  • 掌握 TypeScript 的所有基礎類型及其使用場景
  • 理解類型註解與類型推斷的概念和最佳實踐
  • 學會使用陣列、元組和枚舉等複合類型
  • 了解 any、unknown、void、never 等特殊類型
  • 能夠在實際開發中正確選擇和使用適當的類型

1. 類型系統概述

1.1 為什麼需要類型?

TypeScript 的類型系統是其最核心的特性,它為 JavaScript 帶來了靜態類型檢查的能力。

類型系統的優勢:

  1. 早期錯誤發現 - 在編譯時就能發現類型錯誤
  2. 更好的開發體驗 - IDE 提供精確的自動完成和重構
  3. 程式碼文檔化 - 類型本身就是最好的文檔
  4. 重構安全性 - 大規模重構時的類型保障
  5. 團隊協作 - 明確的介面契約

1.2 類型註解 vs 類型推斷

類型註解 (Type Annotation)

typescript
1let name: string = "Alice";
2let age: number = 25;

類型推斷 (Type Inference)

typescript
1let name = "Alice";    // 推斷為 string
2let age = 25;          // 推斷為 number

TypeScript 會盡可能自動推斷類型,但在某些情況下明確的類型註解是必要的。

2. 基本原始類型

2.1 布林類型 (boolean)

布林類型只有兩個值:truefalse

typescript
1let isDone: boolean = false;
2let isActive: boolean = true;
3
4// 常用於條件判斷
5if (isDone) {
6  console.log("任務完成!");
7}
8
9// 函數返回布林值
10function isEven(num: number): boolean {
11  return num % 2 === 0;
12}

使用場景:

  • 開關狀態
  • 條件判斷
  • 權限檢查
  • 表單驗證結果

2.2 數字類型 (number)

TypeScript 中所有數字都是浮點數,類型為 number

typescript
1// 不同進制的數字
2let decimal: number = 42;
3let hex: number = 0x2a;        // 十六進制
4let binary: number = 0b101010;  // 二進制
5let octal: number = 0o52;       // 八進制
6
7// 浮點數
8let price: number = 99.99;
9let pi: number = 3.14159;
10
11// 特殊數值
12let notANumber: number = NaN;
13let infinity: number = Infinity;
14let negativeInfinity: number = -Infinity;

數學運算:

typescript
1let a: number = 10;
2let b: number = 3;
3
4console.log(a + b);    // 13
5console.log(a - b);    // 7
6console.log(a * b);    // 30
7console.log(a / b);    // 3.3333...
8console.log(a % b);    // 1 (餘數)
9console.log(a ** b);   // 1000 (次方)

2.3 字串類型 (string)

字串類型用於表示文本數據。

typescript
1let color: string = "blue";
2let fullName: string = 'John Doe';
3
4// 模板字串 (Template Literals)
5let age: number = 30;
6let sentence: string = `Hello, my name is ${fullName}.
7I'll be ${age + 1} years old next month.`;
8
9// 字串方法
10let message: string = "Hello World";
11console.log(message.length);           // 11
12console.log(message.toUpperCase());    // "HELLO WORLD"
13console.log(message.toLowerCase());    // "hello world"
14console.log(message.substring(0, 5));  // "Hello"

模板字串的優勢:

  • 支援多行字串
  • 變數插值 ${variable}
  • 表達式計算 ${expression}
  • 更好的可讀性

3. 陣列類型 (Array)

陣列用於存儲同一類型的多個元素。

3.1 陣列定義方式

typescript
1// 方式一:類型[]
2let numbers: number[] = [1, 2, 3, 4, 5];
3let names: string[] = ["Alice", "Bob", "Charlie"];
4let flags: boolean[] = [true, false, true];
5
6// 方式二:Array<類型>
7let scores: Array<number> = [95, 87, 92];
8let colors: Array<string> = ["red", "green", "blue"];

3.2 陣列操作

typescript
1let fruits: string[] = ["apple", "banana"];
2
3// 添加元素
4fruits.push("orange");           // ["apple", "banana", "orange"]
5fruits.unshift("grape");         // ["grape", "apple", "banana", "orange"]
6
7// 移除元素
8let lastFruit = fruits.pop();    // "orange"
9let firstFruit = fruits.shift(); // "grape"
10
11// 查找元素
12let index = fruits.indexOf("banana");  // 1
13let exists = fruits.includes("apple"); // true
14
15// 陣列方法
16let upperFruits = fruits.map(fruit => fruit.toUpperCase());
17let longFruits = fruits.filter(fruit => fruit.length > 5);

3.3 多維陣列

typescript
1// 二維陣列
2let matrix: number[][] = [
3  [1, 2, 3],
4  [4, 5, 6],
5  [7, 8, 9]
6];
7
8// 存取元素
9console.log(matrix[0][1]); // 2
10console.log(matrix[2][0]); // 7

4. 元組類型 (Tuple)

元組允許表示一個已知元素數量和類型的陣列,各元素的類型不必相同。

4.1 基本元組

typescript
1// 定義元組類型
2let person: [string, number] = ["Alice", 25];
3
4// 存取元素
5console.log(person[0]); // "Alice" (string)
6console.log(person[1]); // 25 (number)
7
8// 解構賦值
9let [name, age] = person;
10console.log(name); // "Alice"
11console.log(age);  // 25

4.2 複雜元組

typescript
1// 產品資訊:[ID, 名稱, 價格, 是否有庫存]
2let product: [number, string, number, boolean] = [1, "iPhone", 999, true];
3
4// 座標點:[x, y, z]
5let point3D: [number, number, number] = [10, 20, 30];
6
7// 函數返回多個值
8function getNameAndAge(): [string, number] {
9  return ["Bob", 30];
10}
11
12let [userName, userAge] = getNameAndAge();

4.3 可選元組元素

typescript
1// 可選元素(TypeScript 3.0+)
2let optionalTuple: [string, number?] = ["hello"];
3optionalTuple = ["hello", 42]; // 也可以
4
5// 剩餘元素
6let restTuple: [string, ...number[]] = ["first", 1, 2, 3, 4];

5. 枚舉類型 (Enum)

枚舉允許我們定義一組命名常數,使程式碼更具可讀性。

5.1 數字枚舉

typescript
1enum Direction {
2  Up,    // 0
3  Down,  // 1
4  Left,  // 2
5  Right  // 3
6}
7
8let dir: Direction = Direction.Up;
9console.log(dir); // 0
10
11// 自定義起始值
12enum Status {
13  Pending = 1,
14  Processing, // 2
15  Complete,   // 3
16  Failed      // 4
17}
18
19// 反向映射
20console.log(Status[1]); // "Pending"
21console.log(Status.Processing); // 2

5.2 字串枚舉

typescript
1enum LogLevel {
2  Error = "ERROR",
3  Warn = "WARN",
4  Info = "INFO",
5  Debug = "DEBUG"
6}
7
8function log(message: string, level: LogLevel) {
9  console.log(`[${level}] ${message}`);
10}
11
12log("系統啟動", LogLevel.Info); // [INFO] 系統啟動
13log("發生錯誤", LogLevel.Error); // [ERROR] 發生錯誤

5.3 常數枚舉

typescript
1const enum Colors {
2  Red,
3  Green,
4  Blue
5}
6
7let color = Colors.Red; // 編譯後直接替換為 0

常數枚舉的優勢:

  • 編譯時內聯,減少運行時開銷
  • 更好的性能
  • 適用於不需要反向映射的場景

6. 特殊類型

6.1 any 類型

any 類型可以表示任何類型,關閉了 TypeScript 的類型檢查。

typescript
1let value: any = 42;
2value = "hello";     // OK
3value = true;        // OK
4value = {};          // OK
5value = [];          // OK
6
7// 可以調用任何方法(危險!)
8value.foo.bar.baz;   // 編譯通過,運行時可能錯誤

何時使用 any:

  • 遷移 JavaScript 程式碼
  • 處理動態內容
  • 第三方庫沒有類型定義

避免過度使用:

typescript
1// 不好的做法
2let data: any = fetchUserData();
3
4// 更好的做法
5interface User {
6  id: number;
7  name: string;
8  email: string;
9}
10let data: User = fetchUserData();

6.2 unknown 類型

unknownany 的安全替代方案。

typescript
1let value: unknown;
2
3value = 42;
4value = "hello";
5value = true;
6
7// 不能直接使用,必須先檢查類型
8// value.toUpperCase(); // 錯誤!
9
10// 類型守衛
11if (typeof value === "string") {
12  console.log(value.toUpperCase()); // OK
13}
14
15if (typeof value === "number") {
16  console.log(value.toFixed(2)); // OK
17}

unknown vs any:

  • unknown 更安全,需要類型檢查
  • any 關閉類型檢查,更危險
  • 優先使用 unknown

6.3 void 類型

void 表示沒有返回值的函數。

typescript
1function logMessage(message: string): void {
2  console.log(message);
3  // 沒有 return 語句,或者 return;
4}
5
6function processData(data: any[]): void {
7  data.forEach(item => console.log(item));
8  return; // 可以有空的 return
9}
10
11// void 變數只能賦值 undefined
12let unusable: void = undefined;

6.4 never 類型

never 表示永遠不會出現的值的類型。

typescript
1// 總是拋出錯誤的函數
2function throwError(message: string): never {
3  throw new Error(message);
4}
5
6// 無限循環的函數
7function infiniteLoop(): never {
8  while (true) {
9    // 永遠不會結束
10  }
11}
12
13// 窮盡性檢查
14type Shape = "circle" | "square" | "triangle";
15
16function getArea(shape: Shape): number {
17  switch (shape) {
18    case "circle":
19      return Math.PI * 5 * 5;
20    case "square":
21      return 10 * 10;
22    case "triangle":
23      return 0.5 * 8 * 6;
24    default:
25      // 如果所有情況都處理了,這裡的 shape 是 never
26      const exhaustiveCheck: never = shape;
27      throw new Error(`未處理的形狀: ${exhaustiveCheck}`);
28  }
29}

6.5 null 和 undefined

typescript
1// 在 strictNullChecks: true 下
2let nullValue: null = null;
3let undefinedValue: undefined = undefined;
4
5// 聯合類型
6let nullable: string | null = "hello";
7nullable = null; // OK
8
9let optional: number | undefined = 42;
10optional = undefined; // OK
11
12// 可選屬性
13interface User {
14  name: string;
15  age?: number; // 等同於 age: number | undefined
16}

7. 類型斷言

有時你比 TypeScript 更了解某個值的類型,可以使用類型斷言。

7.1 基本語法

typescript
1// 角括號語法
2let someValue: any = "this is a string";
3let strLength: number = (<string>someValue).length;
4
5// as 語法(推薦,JSX 兼容)
6let strLength2: number = (someValue as string).length;

7.2 實際應用

typescript
1// DOM 操作
2let inputElement = document.getElementById("myInput") as HTMLInputElement;
3inputElement.value = "Hello";
4
5// API 響應
6interface ApiResponse {
7  data: any;
8  status: number;
9}
10
11let response: ApiResponse = await fetch("/api/data").then(r => r.json());
12let userData = response.data as User;

7.3 非空斷言

typescript
1// 非空斷言操作符 !
2let maybeString: string | null = getString();
3let definitelyString: string = maybeString!; // 斷言不為 null
4
5// 謹慎使用,確保值確實不為 null/undefined

8. 類型推斷深入

8.1 最佳通用類型

typescript
1// TypeScript 會推斷最佳通用類型
2let numbers = [1, 2, 3];        // number[]
3let mixed = [1, "hello", true]; // (string | number | boolean)[]
4
5// 空陣列需要明確類型
6let empty: string[] = [];

8.2 上下文類型

typescript
1// 根據上下文推斷類型
2window.onmousedown = function(mouseEvent) {
3  // mouseEvent 被推斷為 MouseEvent
4  console.log(mouseEvent.button);
5};
6
7// 陣列方法的回調函數
8let numbers = [1, 2, 3];
9numbers.map(n => n.toString()); // n 被推斷為 number

互動練習

練習 1:基本類型使用

熟悉基本類型的聲明和使用。

typescript
1type: simple_run
2instruction: 聲明不同類型的變數,觀察 TypeScript 的類型檢查。
3---
4// 聲明基本類型變數
5let userName: string = "Alice";
6let userAge: number = 25;
7let isActive: boolean = true;
8
9console.log(`用戶: ${userName}, 年齡: ${userAge}, 活躍: ${isActive}`);
10
11// 嘗試類型推斷
12let inferredString = "Hello TypeScript"; // 推斷為 string
13let inferredNumber = 42;                 // 推斷為 number
14let inferredBoolean = false;             // 推斷為 boolean
15
16console.log("推斷的類型:");
17console.log(typeof inferredString);  // string
18console.log(typeof inferredNumber);  // number
19console.log(typeof inferredBoolean); // boolean
20
21// 展示類型安全
22// userName = 123; // 這會產生編譯錯誤
23console.log("✅ 基本類型練習完成!");
typescript
1// 這個練習展示了 TypeScript 基本類型的使用
2// 重點概念:
3// 1. 明確的類型註解 vs 類型推斷
4// 2. TypeScript 的類型安全檢查
5// 3. typeof 操作符在運行時檢查類型
6
7// 最佳實踐:
8// - 簡單變數可以依賴類型推斷
9// - 函數參數和返回值建議明確註解
10// - 複雜對象建議使用介面定義

練習 2:陣列操作實戰

學習陣列類型的定義和操作。

typescript
1type: output_check
2instruction: 完成購物車功能,實現商品的添加、移除和計算總價。
3expectedOutput: 購物車商品: iPhone,iPad,MacBook, 總價: $2997
4---
5// 定義商品介面
6interface Product {
7  id: number;
8  name: string;
9  price: number;
10}
11
12// 購物車類
13class ShoppingCart {
14  private items: Product[] = [];
15
16  addItem(product: Product): void {
17    this.items.push(product);
18  }
19
20  removeItem(productId: number): void {
21    this.items = this.items.filter(item => item.id !== productId);
22  }
23
24  getTotalPrice(): number {
25    return this.items.reduce((total, item) => total + item.price, 0);
26  }
27
28  getItemNames(): string[] {
29    return this.items.map(item => item.name);
30  }
31}
32
33// 測試購物車
34const cart = new ShoppingCart();
35
36// 添加商品
37cart.addItem({ id: 1, name: "iPhone", price: 999 });
38cart.addItem({ id: 2, name: "iPad", price: 599 });
39cart.addItem({ id: 3, name: "MacBook", price: 1399 });
40
41// 輸出結果
42const itemNames = cart.getItemNames().join(",");
43const totalPrice = cart.getTotalPrice();
44console.log(`購物車商品: ${itemNames}, 總價: $${totalPrice}`);
typescript
1// 這個練習展示了:
2// 1. 介面定義 - Product 介面定義了商品的結構
3// 2. 陣列類型 - Product[] 表示商品陣列
4// 3. 陣列方法 - push, filter, reduce, map 等
5// 4. 類的使用 - 封裝購物車邏輯
6
7// 關鍵陣列方法:
8// - push(): 添加元素到陣列末尾
9// - filter(): 過濾符合條件的元素
10// - reduce(): 累積計算(如總和)
11// - map(): 轉換陣列元素
12
13// 類型安全的好處:
14// - 編譯時檢查商品結構
15// - 防止錯誤的方法調用
16// - 更好的 IDE 支援

練習 3:元組與解構

掌握元組類型的使用和解構賦值。

typescript
1type: output_check
2instruction: 使用元組表示座標點,實現距離計算功能。
3expectedOutput:A: (0,0),B: (3,4), 距離: 5
4---
5// 定義座標點類型(元組)
6type Point = [number, number];
7
8// 計算兩點間距離的函數
9function calculateDistance(point1: Point, point2: Point): number {
10  const [x1, y1] = point1; // 解構第一個點
11  const [x2, y2] = point2; // 解構第二個點
12  
13  const deltaX = x2 - x1;
14  const deltaY = y2 - y1;
15  
16  return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
17}
18
19// 創建座標點
20const pointA: Point = [0, 0];
21const pointB: Point = [3, 4];
22
23// 計算距離
24const distance = calculateDistance(pointA, pointB);
25
26// 輸出結果
27console.log(`點A: (${pointA[0]},${pointA[1]}), 點B: (${pointB[0]},${pointB[1]}), 距離: ${distance}`);
typescript
1// 元組的優勢:
2// 1. 固定長度和類型順序
3// 2. 比陣列更嚴格的類型檢查
4// 3. 適合表示結構化數據
5
6// 解構賦值的好處:
7// 1. 簡潔的語法
8// 2. 直接提取需要的值
9// 3. 提高程式碼可讀性
10
11// 實際應用場景:
12// - 座標點 [x, y]
13// - 顏色值 [r, g, b]
14// - 函數返回多個值
15// - 鍵值對 [key, value]
16
17// 類型別名 (Type Alias):
18// type Point = [number, number] 創建了可重用的類型定義

練習 4:枚舉實戰應用

使用枚舉管理應用狀態和常數。

typescript
1type: output_check
2instruction: 實現一個任務管理系統,使用枚舉表示任務狀態和優先級。
3expectedOutput: 任務: 學習TypeScript, 狀態: 進行中, 優先級:, 可以開始: true
4---
5// 定義任務狀態枚舉
6enum TaskStatus {
7  Pending = "待處理",
8  InProgress = "進行中", 
9  Completed = "已完成",
10  Cancelled = "已取消"
11}
12
13// 定義優先級枚舉
14enum Priority {
15  Low = 1,
16  Medium = 2,
17  High = 3,
18  Critical = 4
19}
20
21// 任務介面
22interface Task {
23  id: number;
24  title: string;
25  status: TaskStatus;
26  priority: Priority;
27}
28
29// 任務管理器
30class TaskManager {
31  private tasks: Task[] = [];
32
33  addTask(title: string, priority: Priority): void {
34    const task: Task = {
35      id: this.tasks.length + 1,
36      title,
37      status: TaskStatus.Pending,
38      priority
39    };
40    this.tasks.push(task);
41  }
42
43  updateTaskStatus(taskId: number, status: TaskStatus): void {
44    const task = this.tasks.find(t => t.id === taskId);
45    if (task) {
46      task.status = status;
47    }
48  }
49
50  canStartTask(taskId: number): boolean {
51    const task = this.tasks.find(t => t.id === taskId);
52    return task ? task.status === TaskStatus.Pending : false;
53  }
54
55  getTaskInfo(taskId: number): string {
56    const task = this.tasks.find(t => t.id === taskId);
57    if (!task) return "任務不存在";
58    
59    const priorityName = Priority[task.priority] || "未知";
60    const canStart = this.canStartTask(taskId);
61    
62    return `任務: ${task.title}, 狀態: ${task.status}, 優先級: ${priorityName}, 可以開始: ${canStart}`;
63  }
64}
65
66// 測試任務管理器
67const manager = new TaskManager();
68manager.addTask("學習TypeScript", Priority.High);
69manager.updateTaskStatus(1, TaskStatus.InProgress);
70
71console.log(manager.getTaskInfo(1));
typescript
1// 枚舉的最佳實踐:
2
3// 1. 字串枚舉 vs 數字枚舉:
4//    - 字串枚舉:更好的調試體驗,值有意義
5//    - 數字枚舉:支援反向映射,更緊湊
6
7// 2. 枚舉的優勢:
8//    - 集中管理常數
9//    - 類型安全
10//    - 自動完成支援
11//    - 重構友好
12
13// 3. 實際應用:
14//    - 狀態管理(如任務狀態)
15//    - 配置選項
16//    - 錯誤代碼
17//    - API 響應狀態
18
19// 4. 注意事項:
20//    - 避免異構枚舉(混合字串和數字)
21//    - 考慮使用 const enum 優化性能
22//    - 字串枚舉沒有反向映射

練習 5:類型安全的 API 處理

使用 unknown 和類型守衛處理 API 響應。

typescript
1type: output_check
2instruction: 實現類型安全的 API 響應處理,使用 unknown 類型和類型守衛。
3expectedOutput: 用戶資料: {"id":1,"name":"Alice","email":"[email protected]","age":25}
4---
5// 定義用戶介面
6interface User {
7  id: number;
8  name: string;
9  email: string;
10  age: number;
11}
12
13// 類型守衛函數
14function isUser(obj: unknown): obj is User {
15  return (
16    typeof obj === "object" &&
17    obj !== null &&
18    typeof (obj as User).id === "number" &&
19    typeof (obj as User).name === "string" &&
20    typeof (obj as User).email === "string" &&
21    typeof (obj as User).age === "number"
22  );
23}
24
25// 模擬 API 響應處理
26function processApiResponse(response: unknown): User | null {
27  // 使用類型守衛檢查
28  if (isUser(response)) {
29    return response; // 現在 TypeScript 知道這是 User 類型
30  }
31  
32  console.log("無效的用戶資料格式");
33  return null;
34}
35
36// 模擬不同的 API 響應
37const validResponse: unknown = {
38  id: 1,
39  name: "Alice",
40  email: "[email protected]",
41  age: 25
42};
43
44const invalidResponse: unknown = {
45  id: "1", // 錯誤:應該是 number
46  name: "Bob"
47  // 缺少必要欄位
48};
49
50// 處理響應
51const user1 = processApiResponse(validResponse);
52const user2 = processApiResponse(invalidResponse);
53
54if (user1) {
55  console.log(`用戶資料: ${JSON.stringify(user1)}`);
56}
57
58if (user2) {
59  console.log(`用戶資料: ${JSON.stringify(user2)}`);
60} else {
61  console.log("第二個響應無效");
62}
typescript
1// unknown 類型的優勢:
2// 1. 比 any 更安全 - 強制類型檢查
3// 2. 適合處理外部數據(API、用戶輸入)
4// 3. 防止意外的屬性訪問
5
6// 類型守衛 (Type Guards):
7// 1. 用戶定義的類型守衛:obj is Type
8// 2. typeof 守衛:typeof x === "string"
9// 3. instanceof 守衛:x instanceof Date
10// 4. in 操作符:'property' in object
11
12// 最佳實踐:
13// 1. 對外部數據使用 unknown 而不是 any
14// 2. 創建可重用的類型守衛函數
15// 3. 在類型守衛中檢查所有必要屬性
16// 4. 處理邊界情況(null、undefined)
17
18// 實際應用:
19// - API 響應驗證
20// - 用戶輸入驗證
21// - 第三方庫集成
22// - 動態內容處理

練習 6:never 類型與窮盡性檢查

使用 never 類型確保程式碼的完整性。

typescript
1type: output_check
2instruction: 實現一個形狀面積計算器,使用 never 類型進行窮盡性檢查。
3expectedOutput: 圓形面積: 78.54, 正方形面積: 100, 三角形面積: 24
4---
5// 定義形狀類型
6type Shape = 
7  | { kind: "circle"; radius: number }
8  | { kind: "square"; sideLength: number }
9  | { kind: "triangle"; base: number; height: number };
10
11// 面積計算函數
12function calculateArea(shape: Shape): number {
13  switch (shape.kind) {
14    case "circle":
15      return Math.PI * shape.radius * shape.radius;
16    
17    case "square":
18      return shape.sideLength * shape.sideLength;
19    
20    case "triangle":
21      return 0.5 * shape.base * shape.height;
22    
23    default:
24      // 窮盡性檢查:如果所有情況都處理了,這裡的 shape 是 never
25      const exhaustiveCheck: never = shape;
26      throw new Error(`未處理的形狀類型: ${exhaustiveCheck}`);
27  }
28}
29
30// 錯誤處理函數
31function handleError(message: string): never {
32  throw new Error(message);
33}
34
35// 測試不同形狀
36const circle: Shape = { kind: "circle", radius: 5 };
37const square: Shape = { kind: "square", sideLength: 10 };
38const triangle: Shape = { kind: "triangle", base: 8, height: 6 };
39
40try {
41  const circleArea = calculateArea(circle);
42  const squareArea = calculateArea(square);
43  const triangleArea = calculateArea(triangle);
44  
45  console.log(`圓形面積: ${circleArea.toFixed(2)}, 正方形面積: ${squareArea}, 三角形面積: ${triangleArea}`);
46} catch (error) {
47  console.log(`計算錯誤: ${error.message}`);
48}
typescript
1// never 類型的應用場景:
2
3// 1. 窮盡性檢查:
4//    - 確保 switch/if-else 處理所有情況
5//    - 當添加新的聯合類型成員時,編譯器會提醒更新處理邏輯
6
7// 2. 永不返回的函數:
8//    - 總是拋出錯誤的函數
9//    - 無限循環的函數
10
11// 3. 不可達的程式碼:
12//    - 理論上永遠不會執行的程式碼分支
13
14// 聯合類型 (Union Types):
15// - 使用 | 分隔多個類型
16// - 每個成員有判別屬性(如 kind)
17// - TypeScript 可以根據判別屬性縮小類型
18
19// 最佳實踐:
20// 1. 在 switch 語句的 default 中使用 never 檢查
21// 2. 為聯合類型添加判別屬性
22// 3. 使用 never 確保函數不會意外返回
23// 4. 利用 never 進行編譯時完整性檢查

練習 7:類型推斷與斷言

掌握 TypeScript 的類型推斷機制和類型斷言的使用。

typescript
1type: output_check
2instruction: 實現一個通用的數據處理器,展示類型推斷和類型斷言的使用。
3expectedOutput: 處理字串: HELLO WORLD, 處理數字: 42.00, 處理陣列: [1,2,3,4,5]
4---
5// 通用數據處理器
6class DataProcessor {
7  // 類型推斷:根據參數自動推斷返回類型
8  processString(input: string) {
9    return input.toUpperCase(); // 推斷返回 string
10  }
11
12  processNumber(input: number) {
13    return input.toFixed(2); // 推斷返回 string
14  }
15
16  processArray<T>(input: T[]) {
17    return [...input, input[input.length - 1]]; // 推斷返回 T[]
18  }
19
20  // 處理未知類型的數據
21  processUnknown(input: unknown): string {
22    // 使用類型守衛
23    if (typeof input === "string") {
24      return `處理字串: ${this.processString(input)}`;
25    }
26    
27    if (typeof input === "number") {
28      return `處理數字: ${this.processNumber(input)}`;
29    }
30    
31    if (Array.isArray(input)) {
32      // 類型斷言:我們知道這是數字陣列
33      const numberArray = input as number[];
34      const processed = this.processArray(numberArray);
35      return `處理陣列: [${processed.join(",")}]`;
36    }
37    
38    return "未知類型";
39  }
40
41  // DOM 操作中的類型斷言
42  setupInputElement(elementId: string): void {
43    // 類型斷言:我們知道這是 input 元素
44    const element = document.getElementById(elementId) as HTMLInputElement;
45    if (element) {
46      element.value = "預設值";
47      element.addEventListener("change", (e) => {
48        const target = e.target as HTMLInputElement;
49        console.log(`輸入值: ${target.value}`);
50      });
51    }
52  }
53}
54
55// 測試數據處理器
56const processor = new DataProcessor();
57
58// 測試不同類型的數據
59const stringResult = processor.processUnknown("hello world");
60const numberResult = processor.processUnknown(42);
61const arrayResult = processor.processUnknown([1, 2, 3, 4]);
62
63console.log(`${stringResult}, ${numberResult}, ${arrayResult}`);
64
65// 展示類型推斷
66let inferredString = processor.processString("test"); // 推斷為 string
67let inferredNumber = processor.processNumber(3.14159); // 推斷為 string
68let inferredArray = processor.processArray([1, 2, 3]); // 推斷為 number[]
typescript
1// 類型推斷的工作原理:
2
3// 1. 變數初始化:
4//    let x = 5; // 推斷為 number
5//    let y = "hello"; // 推斷為 string
6
7// 2. 函數返回值:
8//    function add(a: number, b: number) {
9//      return a + b; // 推斷返回 number
10//    }
11
12// 3. 上下文類型:
13//    window.onclick = (e) => {
14//      // e 被推斷為 MouseEvent
15//    };
16
17// 類型斷言的使用場景:
18
19// 1. DOM 操作:
20//    const input = document.getElementById("id") as HTMLInputElement;
21
22// 2. API 響應:
23//    const data = response.data as UserData;
24
25// 3. 聯合類型縮小:
26//    const value = getValue() as string;
27
28// 注意事項:
29// 1. 類型斷言不是類型轉換
30// 2. 只能斷言為更具體或更寬泛的類型
31// 3. 謹慎使用,確保斷言的正確性
32// 4. 優先使用類型守衛而不是類型斷言

總結

在這一課中,我們全面學習了 TypeScript 的基礎類型系統:

關鍵要點

  1. 基本類型:boolean、number、string 是最常用的原始類型
  2. 複合類型:陣列、元組、枚舉提供了更豐富的數據結構
  3. 特殊類型:any、unknown、void、never 各有特定的使用場景
  4. 類型安全:unknown 比 any 更安全,應該優先使用
  5. 類型推斷:TypeScript 能自動推斷大部分類型,但關鍵位置仍需明確註解

最佳實踐

  • 啟用 strictNullChecks 提高類型安全性
  • 優先使用 unknown 而不是 any
  • 使用類型守衛進行安全的類型檢查
  • 利用 never 類型進行窮盡性檢查
  • 在函數參數和公共 API 中明確標註類型

下一步

  • 學習更複雜的類型操作
  • 掌握介面和類的使用
  • 了解泛型的強大功能
  • 探索高級類型特性

準備好了嗎?讓我們在下一課學習變數宣告和作用域!