📋 目錄
這篇會帶你把
strict從「設定檔選項」變成可執行的團隊遷移流程,並且用 code review 與 CI 防止回退。
前言
最近很多團隊在談 TypeScript strict,不是因為突然流行,而是因為時程壓力真的到了。TypeScript 6.0 RC(2026-03-06 公告)開始把 strict 預設改為 true,6.0 正式版也已在 2026-03-23 發布。這代表新專案「預設就更嚴格」,但不代表所有舊專案會在同一天自動爆炸。
要先釐清三種情境:
- RC / 正式版新建專案:如果沒明確覆蓋設定,
strict預設會是true。 - 已存在且明確寫
"strict": false的舊專案:行為不會瞬間改變,但技術債會持續累積。 - 既有專案沒寫
strict、只靠舊版預設:升級 compiler 時會面臨行為差異,需要先做風險盤點。
為什麼現在就要處理 strict
strict 的價值不只是「型別更完整」,而是把 production 事故往前移到 PR 階段。對前端團隊來說,這通常直接改善兩件事:
- 重構風險可視化:過去 runtime 才炸的 null/undefined 問題,會在 type-check 就被攔下。
- Review 成本下降:reviewer 不必猜函式參數是否安全,編譯器先做第一層 gate。
如果你把 strict 當作「一次性清債」,專案很容易卡住;把它當作持續降低錯誤注入率的機制,才比較接近真實團隊運作。
strict 到底包含什麼
strict: true 不是單一檢查,而是一組 strict family 旗標。以 TypeScript 6.0 文件來看,常見包含以下項目:
| Flag | 你會感受到的改變 |
|---|---|
noImplicitAny | 不再容許隱含 any 混進資料流 |
noImplicitThis | this 來源不明時會被攔截 |
strictNullChecks | null/undefined 需要顯式處理 |
strictFunctionTypes | 回呼參數型別不相容時會報錯 |
strictBindCallApply | call/apply 參數檢查變嚴格 |
strictPropertyInitialization | class 欄位初始化更明確 |
useUnknownInCatchVariables | catch (err) 預設視為 unknown |
strictBuiltinIteratorReturn | iterator return 型別推論更嚴謹 |
alwaysStrict | emit "use strict" 並以 strict mode 解析 |
以上是目前最常用到的 strict family,實際行為仍以當前 TypeScript 版本文件為準。
遷移順序:先收斂錯誤面,再提高門檻
實務上不建議全專案直接翻 "strict": true。比較穩的路線是分階段降低爆炸半徑。
Phase 0: 盤點現況與邊界
{
"compilerOptions": {
"strict": false,
"noEmit": true
}
}
先確認 tsc --noEmit 能穩定跑完,並且把目前錯誤分類(nullish、implicit any、函式參數不相容、第三方型別不完整)。這一步的目標不是修完,而是建立 backlog。
Phase 1: 先開 strictNullChecks + noImplicitAny
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true,
"noImplicitAny": true
}
}
這兩個 flag 通常最影響品質,也最容易在 code review 形成共識。等錯誤數量降到可控後,再往下一階段走。
Phase 2: 打開函式與呼叫安全
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictBindCallApply": true
}
}
這階段會讓高階函式、SDK wrapper、utility 層出現最多改動,建議搭配 pair review。
Phase 3: 收斂到 strict: true
{
"compilerOptions": {
"strict": true
}
}
最後才改成單一 strict: true,避免長期維護一串手動旗標造成認知負擔。
常見爆點 1: strictFunctionTypes 不是形式檢查,而是可避免 runtime crash
下面是官方行為可以重現的例子。
function acceptStringOnly(x: string) {
console.log(x.toLowerCase());
}
type StringOrNumberHandler = (value: string | number) => void;
// strictFunctionTypes 開啟後,這行會報錯
// 因為呼叫端可能傳 number,但 acceptStringOnly 只吃 string
const handler: StringOrNumberHandler = acceptStringOnly;
handler(42); // 如果放行,runtime 會在 toLowerCase 爆掉
這不是「型別系統太嚴」,而是把本來就不安全的 assignability 擋下來。尤其在 callback-heavy 程式碼(event handlers、表單驗證、資料轉換 pipeline)很重要。
常見爆點 2: strictBindCallApply 讓 call/apply 回到可審查狀態
很多 codebase 會在 util 層大量用 call/apply,如果沒型別檢查,參數數量或 union literal 很容易悄悄失真。
function formatPrice(currency: "TWD" | "USD", amount: number) {
return `${currency} ${amount.toFixed(2)}`;
}
formatPrice.call(undefined, "TWD", 99); // ✅
formatPrice.call(undefined, "JPY", 99); // ❌ "JPY" 不在 union
formatPrice.apply(undefined, ["USD", 120]); // ✅
formatPrice.apply(undefined, ["USD"]); // ❌ 少一個 amount
遷移時可以先鎖定「共用 helper + SDK adapter + API client」三類模組,通常能最快看到收益。
常見爆點 3: value == null 與 ?? 不是互斥,而是語義不同
過去很多文章把這題寫成「一律用 ===」,實務上不精準。你要先決定語義。
function normalizeName(input: string | null | undefined) {
if (input == null) {
// 這裡是刻意同時攔 null 與 undefined
return "Anonymous";
}
return input.trim();
}
function displayQuota(quota: number | null | undefined) {
// 只在 nullish 時給預設值,不影響 0
return quota ?? 100;
}
規則很簡單:
- 你要做 nullish guard(同時處理
null | undefined)時,value == null可以是可讀又精準的寫法。 - 你要做 fallback expression 時,優先用
??,避免誤傷0、""、false。
把 strict 寫進 Code Review 與 CI
只改 tsconfig 不夠,流程才是關鍵。下面是一個前端團隊可直接落地的最小模板。
PR Checklist
- 新增或修改的 public function 不可引入隱含
any。 @ts-ignore必須附 issue 連結與移除期限。as unknown as視為例外寫法,需在 PR 描述交代原因。- 若動到
call/apply,需貼出對應型別測試或編譯錯誤截圖。
CI Gate
pnpm tsc --noEmit
pnpm eslint .
pnpm test
如果是 monorepo,建議先做增量 gate(只擋新變更),再逐步擴大到全 repo。
常見問題 / 注意事項
1. 我們是舊專案,能不能永遠維持 strict: false?
可以,但每次升級 TypeScript、框架或第三方型別時,整體維護成本通常會更高,尤其在多人協作下,錯誤會更晚被發現。
2. strict 一開就 800 個錯誤,怎麼辦?
先分桶,不要硬吃:
nullish類(高風險,先修)implicit any類(高頻,建立範本後批次修)- 第三方型別缺口(可先寫 local declaration 或 wrapper)
總結
strict migration 的核心不是「把錯誤修到 0」,而是建立一條不會回退的工程路徑:先分階段降低風險,再用 review 規則與 CI 保持成果。
下一步建議是先建立 strict 錯誤分類清單(至少分成 nullish / any / function assignment 三類),再把 strictNullChecks 與 noImplicitAny 納入正式 gate。