TYPESCRIPT CORE · 93
Narrowing、unknown、null:用 runtime evidence 縮小型別
TypeScript 最有價值的能力之一,不是你「宣告得很有自信」,而是它要求你在使用不確定資料前先取得 evidence。
Learning outcomes
- 能用
typeof、null check、property check 做 narrowing。 - 能寫 type guard 把
unknown驗證成 domain type。 - 能正確使用 optional chaining / nullish coalescing。
- 能分辨 assertion 與 validation。
1. Narrowing:union 不是問題,沒證據就亂用才是
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
進入 if branch 後,TypeScript 根據 runtime check 知道 value 是 string;else path 剩下 number。
2. unknown API data:先驗最基本 shape
function isRecord(value: unknown):
value is Record<string, unknown> {
return typeof value === "object"
&& value !== null;
}
typeof null 歷史上是 "object",所以一定要另外排除 null。
3. 寫一個真正的 Course type guard
function isCourse(value: unknown): value is Course {
if (!isRecord(value)) return false;
return typeof value.id === "number"
&& typeof value.title === "string"
&& (
typeof value.score === "number"
|| value.score === null
);
}
const raw: unknown = await loadCourse(1);
if (!isCourse(raw)) {
throw new Error("Invalid course payload");
}
// 這裡 raw 已被 narrow 成 Course
console.log(raw.title);
4. Assertion vs validation
raw as Course你告訴 checker「相信我」。runtime 沒有新增任何檢查。
isCourse(raw)真的執行 runtime checks,通過後 checker 才縮小型別。
5. Optional chaining 與 nullish coalescing
const city =
user.profile?.address?.city
?? "Unknown";
?. 是安全地在 null/undefined 時停止 property access;?? 只在左邊是 null/undefined 時使用 fallback。
6. Project checkpoint:Typed Course API Client v3
async function fetchCourse(id: number): Promise<Course> {
const raw = await loadCourse(id);
if (!isCourse(raw)) {
throw new Error("Invalid API payload");
}
return raw;
}
async function refreshCourse(id: number) {
state = { kind: "loading" };
try {
const course = await fetchCourse(id);
state = {
kind: "success",
data: course
};
} catch (error) {
state = {
kind: "error",
message:
error instanceof Error
? error.message
: "Unknown error"
};
}
}
現在外部 unknown → validation → internal trusted Course 的 boundary 才真正成立。
7. Debug evidence:型別「看起來對」但 runtime 還是炸
如果 code 裡用了大量 as、any,type checker 的綠燈不能當 runtime safety evidence。第一步應檢查 boundary 上真正收到的 payload。
Knowledge check
- 為什麼只檢查
typeof value === "object"不夠? - 把
isCourse()加上 score 0–100 的 runtime range check。 - 解釋
as Course與 type guard 的差別。 - TypeScript Lab 中刻意讓 API payload 不符合宣告,觀察 compile 與 runtime 的邊界。