測驗:Union、Literal 與型別收窄
共 5 題,點選答案後會立即顯示結果
1. 在 TypeScript 中,string | number 的 | 代表什麼意思?
2. 以下程式碼中,type Status = "loading" | "success" | "error" 屬於什麼型別?
3. 為什麼以下程式碼會報錯?
4. 看以下程式碼,TypeScript 如何判斷 animal 是 Fish 還是 Bird?
5. 以下程式碼中,user?.name ?? "匿名" 的完整意思是什麼?
一句話說明
讓一個變數可以是好幾種型別,但 TypeScript 會逼你「確認清楚了」才能用。
為什麼要學這個?
你請 AI 幫你寫 TypeScript 時,產出的程式碼裡很常出現 string | number、if (typeof x === "string") 這類寫法。這不是 AI 在搞花樣 — 這是 TypeScript 的核心機制。看不懂這些,你就很難判斷 AI 寫的程式碼到底對不對。
1. Union 型別:「可以是 A 或 B」
最小範例
let value: string | number;
value = "hello"; // OK
value = 42; // OK
value = true; // 錯誤!只能是 string 或 number
Code language: JavaScript (javascript)逐行翻譯
let value: string | number;
// ↑ 這個 | 念作「或」
// 意思是:value 可以是文字,也可以是數字
Code language: JavaScript (javascript)一句話:| 就是「或」的意思。string | number 代表「可以是文字或數字」。
常見變化
AI 最常產出的幾種 Union 寫法:
變化 1:允許 null
let user: User | null;
Code language: JavaScript (javascript)翻譯:user 可以是一個 User 物件,也可以是 null(還沒有值)。
變化 2:函式的參數
function printId(id: string | number) {
console.log(id);
}
Code language: JavaScript (javascript)翻譯:printId 這個函式接受文字或數字當 id。
變化 3:函式的回傳值
function find(id: number): User | undefined {
// ...
}
Code language: JavaScript (javascript)翻譯:find 可能回傳一個 User,也可能回傳 undefined(找不到)。
2. Literal 型別:「只能是這幾個特定值」
最小範例
type Direction = "up" | "down" | "left" | "right";
let dir: Direction;
dir = "up"; // OK
dir = "diagonal"; // 錯誤!只能是那四個值之一
Code language: JavaScript (javascript)逐行翻譯
type Direction = "up" | "down" | "left" | "right";
// ↑ 定義一個型別叫 Direction
// ↑ 它只能是這四個字串之一
let dir: Direction;
// ↑ dir 只接受 "up"、"down"、"left"、"right"
Code language: JavaScript (javascript)一句話:Literal 型別就是「把允許的值一個一個列出來」。不是那幾個值,TypeScript 就報錯。
常見變化
變化 1:狀態值
type Status = "loading" | "success" | "error";
Code language: JavaScript (javascript)翻譯:狀態只能是這三種之一,不會出現打錯字的情況。
變化 2:數字 Literal
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
翻譯:骰子只能是 1 到 6。
變化 3:搭配函式參數
function move(direction: "up" | "down" | "left" | "right") {
// ...
}
move("up"); // OK
move("forward"); // 錯誤!
Code language: JavaScript (javascript)翻譯:move 只接受那四個方向的字串,別的一律不行。
3. 型別收窄(Narrowing):「先檢查,再使用」
這是整篇最重要的概念。
問題:Union 型別不能直接用
function printLength(value: string | number) {
console.log(value.length);
// ↑ 錯誤!number 沒有 .length
}
Code language: JavaScript (javascript)TypeScript 不知道 value 是 string 還是 number,所以不讓你直接用 .length(因為數字沒有 .length)。
解法:先用 typeof 檢查
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // OK!TypeScript 知道這裡是 string
} else {
console.log(value); // 這裡 TypeScript 知道是 number
}
}
Code language: JavaScript (javascript)一句話:TypeScript 會「跟著你的 if 判斷走」。你檢查了是 string,它就讓你當 string 用。這就是「收窄」– 把範圍縮小。
4. typeof 收窄:最常見的檢查方式
最小範例
function process(input: string | number | boolean) {
if (typeof input === "string") {
// 這裡 input 是 string
console.log(input.toUpperCase());
} else if (typeof input === "number") {
// 這裡 input 是 number
console.log(input.toFixed(2));
} else {
// 這裡 input 是 boolean
console.log(input ? "yes" : "no");
}
}
Code language: JavaScript (javascript)翻譯
typeof input === "string" // input 是不是文字?
typeof input === "number" // input 是不是數字?
typeof input === "boolean" // input 是不是布林值?
Code language: JavaScript (javascript)一句話:typeof 問的是「這個值是什麼基本型別」。TypeScript 看到你用了 typeof 檢查,就自動知道在那個 if 區塊裡,變數是什麼型別。
5. in 收窄:「這個物件有沒有某個屬性」
最小範例
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // TypeScript 知道這是 Fish
} else {
animal.fly(); // TypeScript 知道這是 Bird
}
}
Code language: JavaScript (javascript)逐行翻譯
"swim" in animal
// ↑ 問:animal 這個物件裡面有沒有 "swim" 這個屬性?
// 如果有,TypeScript 就推斷它是 Fish
Code language: JavaScript (javascript)一句話:"屬性名" in 物件 就是在問「這個物件有沒有這個屬性」。有的話,TypeScript 就知道它是哪個型別。
6. instanceof 收窄:「這個是不是某個 class 的實例」
最小範例
function logDate(value: string | Date) {
if (value instanceof Date) {
console.log(value.getFullYear()); // TypeScript 知道是 Date
} else {
console.log(value.toUpperCase()); // TypeScript 知道是 string
}
}
Code language: JavaScript (javascript)翻譯
value instanceof Date
// ↑ 問:value 是不是一個 Date 物件?
Code language: JavaScript (javascript)一句話:instanceof 用來檢查「是不是某個 class 製造出來的」。typeof 管基本型別(string、number),instanceof 管 class 實例(Date、Error、自定義 class)。
7. 可辨識聯合(Discriminated Unions):靠共同屬性分辨
這是 AI 產出的程式碼裡非常常見的模式。
最小範例
type Success = { status: "success"; data: string };
type Failure = { status: "error"; message: string };
type Result = Success | Failure;
function handle(result: Result) {
if (result.status === "success") {
console.log(result.data); // OK!TypeScript 知道這是 Success
} else {
console.log(result.message); // OK!TypeScript 知道這是 Failure
}
}
Code language: JavaScript (javascript)逐行翻譯
type Success = { status: "success"; data: string };
// ↑ status 固定是 "success" 這個字串(Literal 型別)
type Failure = { status: "error"; message: string };
// ↑ status 固定是 "error"
type Result = Success | Failure;
// ↑ Result 是 Success 或 Failure
if (result.status === "success") {
// ↑ 用共同的 status 屬性來分辨是哪一種
// TypeScript 看到 status 是 "success",就知道整個物件是 Success 型別
}
Code language: JavaScript (javascript)一句話:幾個型別共用一個屬性(如 status),但值不同。TypeScript 根據那個屬性的值,就能判斷是哪個型別。
AI 常用的場景
API 回應
type ApiResponse =
| { ok: true; data: User[] }
| { ok: false; error: string };
Code language: JavaScript (javascript)翻譯:API 回來的東西,不是成功帶資料,就是失敗帶錯誤訊息。用 ok 欄位來分辨。
Redux Action(狀態管理)
type Action =
| { type: "ADD_TODO"; text: string }
| { type: "TOGGLE_TODO"; id: number }
| { type: "DELETE_TODO"; id: number };
Code language: JavaScript (javascript)翻譯:三種操作,用 type 欄位來分辨是哪一種。
8. null 和 undefined 的處理
TypeScript 預設不允許把 null 或 undefined 塞給其他型別。你必須明確宣告並處理。
最小範例
function greet(name: string | null) {
if (name !== null) {
console.log(`Hello, ${name}`); // TypeScript 知道這裡不是 null
} else {
console.log("Hello, stranger");
}
}
Code language: JavaScript (javascript)常見寫法速查
// 寫法 1:if 判斷
if (value !== null && value !== undefined) {
// 安全使用 value
}
// 寫法 2:可選鏈(Optional Chaining)
const name = user?.name;
// ↑ 如果 user 是 null/undefined,name 就是 undefined,不會報錯
// 寫法 3:空值合併(Nullish Coalescing)
const name = user?.name ?? "匿名";
// ↑ 如果 user?.name 是 null 或 undefined,就用 "匿名"
// 寫法 4:非空斷言(謹慎使用!)
const name = user!.name;
// ↑ 告訴 TypeScript「我保證 user 不是 null」
// 如果你猜錯了,執行時會爆掉
Code language: JavaScript (javascript)翻譯對照
| 代碼 | 翻譯 |
|---|---|
user?.name |
如果 user 存在,取 name;否則回傳 undefined |
name ?? "預設" |
如果 name 是 null 或 undefined,用 “預設” |
user!.name |
我保證 user 不是 null(危險,少用) |
必看懂 vs 知道就好
必看懂(AI 產出的程式碼天天都有)
| 語法 | 範例 | 意思 |
|---|---|---|
| Union 型別 | string \| number |
可以是文字或數字 |
| Literal 型別 | "success" \| "error" |
只能是這幾個值 |
| typeof 收窄 | if (typeof x === "string") |
確認是什麼基本型別 |
| in 收窄 | if ("name" in obj) |
確認物件有沒有某屬性 |
| 可辨識聯合 | if (result.status === "ok") |
用共同屬性分辨型別 |
| 可選鏈 | user?.name |
安全存取,不怕 null |
| 空值合併 | value ?? "預設" |
null/undefined 時用預設值 |
知道就好(遇到再查)
- 自訂型別守衛(Type Guard):
function isFish(pet: Fish | Bird): pet is Fish— 自己寫一個函式來判斷型別。 - never 型別:在窮舉所有可能後出現的型別,代表「不應該走到這裡」。
- as 型別斷言:
const x = value as string— 強制告訴 TypeScript 這是什麼型別,除非你很確定,否則不要亂用。
Vibe Coder 檢查點
看到 AI 產出的程式碼裡有 Union 型別或型別收窄時,確認以下幾點:
- [ ] Union 型別的每一種情況都有處理嗎?(比如
string | number,有沒有分別處理 string 和 number 的情況?) - [ ] 有用到
!(非空斷言)嗎?如果有,AI 真的保證那個值不會是 null 嗎? - [ ] 可辨識聯合的每個 case 都有覆蓋到嗎?(比如 API 回應有 success 和 error,兩種都有處理嗎?)
- [ ] 可選鏈
?.後面的邏輯對嗎?(如果值是 undefined,後續有沒有正確處理?)
總結
| 概念 | 一句話說明 |
|---|---|
| Union 型別 | 一個變數可以是好幾種型別,用 \| 隔開 |
| Literal 型別 | 把允許的值一個一個列出來 |
| 型別收窄 | 用 if 檢查後,TypeScript 自動知道是哪種型別 |
| typeof | 檢查基本型別(string、number、boolean) |
| in | 檢查物件有沒有某個屬性 |
| instanceof | 檢查是不是某個 class 的實例 |
| 可辨識聯合 | 用共同屬性(如 status、type)的值來分辨型別 |
| ?. 和 ?? | 安全處理 null/undefined |
核心心法:TypeScript 的 Union 型別是「先放寬,再收窄」。宣告時允許多種型別,使用時逼你先確認是哪一種。看到 AI 寫的那些 if 判斷,多半是 TypeScript 逼它寫的 — 不寫就會報錯。
進階測驗:Union、Literal 與型別收窄
共 5 題,包含情境題與錯誤診斷題。