本文 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 any、as unknown 會掩蓋錯誤。 |
只在確信安全的情況下使用,並在代碼註解說明原因。 |
| 可選屬性未處理 | 讀取未定義的屬性會得到 undefined,導致 runtime error。 |
使用 ?.(optional chaining)或提前檢查。 |
| 函式參數順序錯亂 | 位置參數較多時,容易把型別寫錯。 | 使用 物件參數(named parameters)提升可讀性。 |
| Enum 與字串常量混用 | Enum 會產生額外的 JavaScript 代碼,過度使用會增加 bundle 大小。 | 若只需要字串集合,使用 as const 的字面量型別。 |
最佳實踐
- 啟用嚴格模式:
"strict": true能一次開啟多項檢查,強化安全性。 - 盡量使用
readonly:防止意外變更資料結構。 - 分層定義型別:在
types/目錄下集中管理介面與型別別名,保持專案一致性。 - 利用編輯器的自動完成:VS Code + TypeScript 會在你輸入變數時即時提示型別錯誤,別忽視這個強大工具。
- 撰寫單元測試:即使有型別檢查,仍建議使用 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 type 與 import type 明確界定公共型別。 |
總結
型別檢查是 TypeScript 最核心的價值之一,它讓 開發者在寫程式的同時就能捕捉錯誤,減少在執行階段才發現問題的成本。透過本文的 基礎概念、實用範例、常見陷阱 與 最佳實踐,你應該已經能在日常開發中自信地使用:
- 型別註記與型別推論保障變數正確性
- 介面、型別別名、泛型建構彈性與可重用的結構
- Union / Intersection處理複雜型別需求
- 型別守衛與自訂保護讓程式碼在執行階段同樣安全
在未來的專案裡,記得 開啟嚴格模式、避免 any、善用編輯器的型別提示,讓 TypeScript 成為你開發的最佳夥伴,寫出更可靠、更易維護的程式碼。祝你在 TypeScript 的旅程中玩得開心、寫得順利! 🎉