本文 AI 產出,尚未審核

TypeScript 基礎概念:型別檢查(Type Checking)

簡介

在 JavaScript 的世界裡,變數的型別是動態決定的,這雖然讓開發初期上手快速,但隨著專案規模增長,型別錯誤不易追蹤的 bug 也會隨之而來。TypeScript 透過靜態型別檢查(static type checking)在編譯階段就把潛在的型別問題挑出,讓開發者在寫程式時即能獲得更明確的提示與自動完成支援,提升程式碼的可讀性與可維護性。

本篇文章將從 型別檢查的原理常用語法實務範例,到 常見陷阱與最佳實踐,一步步帶你了解在日常開發中如何善用 TypeScript 的型別系統,從而寫出更安全、更可靠的程式碼。


核心概念

1. 靜態型別 vs 動態型別

  • 靜態型別:在編譯階段就已確定變數的型別,若型別不符會直接報錯。
  • 動態型別:在執行階段才決定型別,錯誤往往在程式跑到那一行才會顯現。

TypeScript 把 JavaScript 變成一個 靜態型別 的超集合,讓開發者在編輯器裡就能即時看到型別錯誤。

2. 基本型別(Primitive Types)

型別 說明 範例
number 整數或浮點數 let age: number = 30;
string 字串 let name: string = "Alice";
boolean 布林值 let isDone: boolean = false;
null / undefined 空值 let empty: null = null;
bigint 任意大小整數 let big: bigint = 123n;
symbol 唯一且不可變的值 let sym = Symbol('id');

3. 型別註記(Type Annotation)

在變數、函式參數或回傳值後加上 : + 型別,即可告訴編譯器「這裡應該是什麼型別」。

let count: number = 0;            // 變數
function greet(name: string): void { // 參數與回傳值
  console.log(`Hello, ${name}`);
}

4. 型別推論(Type Inference)

若未明確標註,TypeScript 會根據賦值自動推斷型別。

let message = "Welcome"; // 推斷為 string
// message = 123; // ❌ 編譯錯誤:不能將 number 賦值給 string

5. 物件型別與介面(Interface)

介面提供結構化的型別描述,常用於定義物件的屬性與方法。

interface User {
  id: number;
  name: string;
  email?: string; // 可選屬性
}

const alice: User = {
  id: 1,
  name: "Alice"
  // email 可不寫
};

6. 陣列與 Tuple

  • 陣列:同一型別的元素集合。
  • Tuple:固定長度、各位置型別可能不同的陣列。
let numbers: number[] = [1, 2, 3];
let tuple: [string, number] = ["Bob", 42];

7. Union 與 Intersection

  • Union (|):變數可屬於多種型別之一。
  • Intersection (&):變數同時擁有多個型別的屬性。
type ID = string | number; // Union
type Admin = { role: "admin" } & User; // Intersection

程式碼範例

範例 1:函式型別檢查與預設參數

// 函式接受兩個 number,若第二個未提供則預設為 0
function add(a: number, b: number = 0): number {
  return a + b;
}

// 正確使用
const sum = add(5, 10); // 15

// 錯誤示範,編譯階段就會報錯
// const bad = add("5", 10); // ❌ Type '"5"' is not assignable to type 'number'

範例 2:物件解構與型別別名(type alias)

type Point = {
  x: number;
  y: number;
};

function distance(p1: Point, p2: Point): number {
  const { x: x1, y: y1 } = p1;
  const { x: x2, y: y2 } = p2;
  return Math.hypot(x2 - x1, y2 - y1);
}

const a: Point = { x: 0, y: 0 };
const b: Point = { x: 3, y: 4 };
console.log(distance(a, b)); // 5

範例 3:使用 Union 型別處理多種輸入

function formatId(id: string | number): string {
  // 先做型別縮小(type narrowing)
  if (typeof id === "number") {
    return `ID-${id.toString().padStart(6, "0")}`;
  }
  return `ID-${id.toUpperCase()}`;
}

console.log(formatId(23));      // "ID-000023"
console.log(formatId("abc"));   // "ID-ABC"

範例 4:泛型(Generics)與型別安全的資料結構

// 一個簡易的 Stack
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }
}

const numberStack = new Stack<number>();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop()); // 20 (型別為 number)

const stringStack = new Stack<string>();
stringStack.push("first");
stringStack.push("second");
console.log(stringStack.peek()); // "second" (型別為 string)

範例 5:型別守衛(Type Guard)與自訂型別保護

interface Cat {
  kind: "cat";
  meow(): void;
}
interface Dog {
  kind: "dog";
  bark(): void;
}
type Pet = Cat | Dog;

// 自訂型別守衛
function isCat(pet: Pet): pet is Cat {
  return pet.kind === "cat";
}

function speak(pet: Pet) {
  if (isCat(pet)) {
    pet.meow(); // TypeScript 知道此時 pet 為 Cat
  } else {
    pet.bark(); // 此時 pet 為 Dog
  }
}

常見陷阱與最佳實踐

陷阱 說明 解決方案
any 失控 使用 any 會讓型別檢查失效,等於回到 JavaScript。 盡量避免 any,改用 unknown 或精確的型別。
過度斷言(type assertion) as anyas unknown 會掩蓋錯誤。 只在確信安全的情況下使用,並在代碼註解說明原因。
可選屬性未處理 讀取未定義的屬性會得到 undefined,導致 runtime error。 使用 ?.(optional chaining)或提前檢查。
函式參數順序錯亂 位置參數較多時,容易把型別寫錯。 使用 物件參數(named parameters)提升可讀性。
Enum 與字串常量混用 Enum 會產生額外的 JavaScript 代碼,過度使用會增加 bundle 大小。 若只需要字串集合,使用 as const 的字面量型別。

最佳實踐

  1. 啟用嚴格模式"strict": true 能一次開啟多項檢查,強化安全性。
  2. 盡量使用 readonly:防止意外變更資料結構。
  3. 分層定義型別:在 types/ 目錄下集中管理介面與型別別名,保持專案一致性。
  4. 利用編輯器的自動完成:VS Code + TypeScript 會在你輸入變數時即時提示型別錯誤,別忽視這個強大工具。
  5. 撰寫單元測試:即使有型別檢查,仍建議使用 Jest 或 Vitest 進行行為驗證,兩者相輔相成。

實際應用場景

場景 為何需要型別檢查 典型寫法
REST API 回傳資料 伺服器回傳的 JSON 可能缺欄位或型別不符。 使用 interface + type guard 解析回傳值。
大型前端框架(React / Vue) 多層元件傳遞 props,錯誤的型別會導致 UI 異常。 為 props 定義 interface,搭配 React.FC<Props>
Node.js 後端服務 資料庫模型、環境變數等必須在啟動時驗證。 使用 zod / io-ts 搭配 TypeScript 型別推斷。
函式庫(Library)開發 使用者會依賴你的型別說明,錯誤會直接影響他們的編譯。 為每個公開 API 撰寫完整的型別定義與文件。
多人協作專案 不同開發者負責不同模組,型別能作為「契約」防止衝突。 透過 export typeimport type 明確界定公共型別。

總結

型別檢查是 TypeScript 最核心的價值之一,它讓 開發者在寫程式的同時就能捕捉錯誤,減少在執行階段才發現問題的成本。透過本文的 基礎概念實用範例常見陷阱最佳實踐,你應該已經能在日常開發中自信地使用:

  • 型別註記型別推論保障變數正確性
  • 介面型別別名泛型建構彈性與可重用的結構
  • Union / Intersection處理複雜型別需求
  • 型別守衛自訂保護讓程式碼在執行階段同樣安全

在未來的專案裡,記得 開啟嚴格模式避免 any善用編輯器的型別提示,讓 TypeScript 成為你開發的最佳夥伴,寫出更可靠、更易維護的程式碼。祝你在 TypeScript 的旅程中玩得開心、寫得順利! 🎉