本文 AI 產出,尚未審核

TypeScript 編譯與設定:compilerOptions.strict 完全指南


簡介

在大型 JavaScript 專案中,型別安全是維護程式碼品質、降低錯誤率的關鍵。TypeScript 之所以受到廣大開發者青睞,主要是因為它的編譯器(tsc)提供了一系列可自訂的檢查規則,而 compilerOptions.strict 正是其中最具影響力的一把「安全開關」。

開啟 strict 後,編譯器會一次啟用多項嚴格模式(strictNullChecksstrictFunctionTypesstrictPropertyInitializationnoImplicitAnynoImplicitThisalwaysStrict 等),讓 每一行程式碼都必須在型別上明確、合理。雖然在開發初期會遇到較多的型別錯誤,但長遠來看,這能顯著減少執行時的不可預期行為,提升團隊協作的可預測性。

本篇文章將逐一說明 strict 的組成、實作方式與常見陷阱,並提供實務範例,協助你在專案中安全、有效地啟用這項功能。


核心概念

1. strict 是什麼?

strict 本身不是一個獨立的檢查規則,而是一個總開關,等同於同時開啟以下子選項:

子選項 功能說明
strictNullChecks 嚴格的 null 與 undefined 判斷。必須明確處理 null/undefined,否則會產生型別錯誤。
strictFunctionTypes 函式參數雙向協變檢查,確保函式型別相容性更精確。
strictPropertyInitialization 類別屬性必須在建構子或宣告時初始化,避免未賦值屬性造成的執行時錯誤。
noImplicitAny 隱式 any 會被視為錯誤,要求開發者必須給予明確型別。
noImplicitThis 隱式 this 會被視為錯誤,避免在函式內部誤用 this
alwaysStrict 自動加入 "use strict" 到輸出檔,確保 JavaScript 嚴格模式。
strictBindCallApply bindcallapply 的型別檢查更嚴格。
strictSaveFunctionTypes(自 TS 5.0 起) 保存函式型別 以提升型別推斷的準確度。

只要在 tsconfig.json 中將 strict: true,以上全部會一次啟用。若要微調,可個別關閉("strictNullChecks": false)或自行開啟其他嚴格選項。


2. 為什麼要開啟 strict

  1. 提前捕捉錯誤:大多數 bug(例如未處理 null、錯誤的函式參數)會在編譯階段被檢測出,減少部署後的緊急修復。
  2. 提升自動完成與型別推斷:嚴格模式讓編輯器(VS Code、WebStorm)能更精準地提供 IntelliSense,提升開發效率。
  3. 文件化程式碼意圖:每個變數、函式的型別都必須明確,讓新加入的同事能快速理解程式邏輯。
  4. 支援未來的 JavaScript 標準alwaysStrict 讓產出檔自動使用 ES5+ 的嚴格模式,減少舊版行為的衝突。

3. 如何在專案中啟用 strict

在專案根目錄建立(或編輯)tsconfig.json,加入以下設定:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "strict": true,               // 開啟全部嚴格檢查
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

小技巧:若團隊尚未全部適應,可以先設定 "strict": false,然後逐項開啟(如 noImplicitAny)作為漸進式遷移的策略。


程式碼範例

以下示範在開啟 strict 前後的差異,讓你直觀感受到每個子選項的作用。

範例 1:strictNullChecks

// 沒有 strictNullChecks 時
function greet(name: string) {
  console.log(`Hello, ${name.toUpperCase()}`);
}
greet(null); // ✅ 編譯通過,執行時會拋出錯誤
// 開啟 strictNullChecks 後
function greet(name: string) {
  // 編譯器會提示 name 可能為 null
  console.log(`Hello, ${name.toUpperCase()}`);
}
greet(null); // ❌ 編譯錯誤:Argument of type 'null' is not assignable to parameter of type 'string'.

解決方式:使用聯合型別或非空斷言:

function greet(name: string | null) {
  if (name) {
    console.log(`Hello, ${name.toUpperCase()}`);
  } else {
    console.log('Hello, Guest');
  }
}

範例 2:noImplicitAny

function add(a, b) {
  return a + b;
}
add(1, 2); // ✅ 沒有錯誤,a、b 被推斷為 any
// 開啟 noImplicitAny 後
function add(a, b) {
  return a + b;
}
// ❌ 錯誤:Parameter 'a' implicitly has an 'any' type.

解決方式:明確指定型別或使用泛型:

function add(a: number, b: number): number {
  return a + b;
}

範例 3:strictPropertyInitialization

class User {
  name: string;          // 未初始化
  age?: number;          // 可選屬性

  constructor(name: string) {
    this.name = name;    // 只在建構子裡賦值
  }
}

strictPropertyInitialization 開啟時,若有屬性未在宣告或建構子中保證初始化,編譯會報錯。解決方法:

class User {
  name: string;
  age?: number;
  isActive: boolean = false; // 直接初始化

  constructor(name: string) {
    this.name = name;
  }
}

範例 4:strictFunctionTypes

type Handler = (event: MouseEvent) => void;

function register(handler: Handler) {
  // ...
}

// 允許傳入更寬鬆的型別(舊行為)
register((e: Event) => console.log(e.type)); // ✅

strictFunctionTypes 開啟後,上例會 編譯錯誤,因為 Event 不是 MouseEvent 的子型別。正確寫法:

register((e: MouseEvent) => console.log(e.button));

範例 5:noImplicitThis

function Counter(this: { count: number }) {
  this.count++;
}
Counter(); // ❌ 編譯錯誤:'this' implicitly has type 'any'

解決方式:使用箭頭函式或明確宣告 this 型別:

function Counter(this: { count: number }) {
  this.count++;
}
const obj = { count: 0 };
Counter.call(obj); // 正確

常見陷阱與最佳實踐

陷阱 說明 解法
過度使用 any 為了快速通過編譯,直接宣告 any,失去型別保護。 使用 unknown具體型別,必要時搭配型別斷言。
忽略 null/undefined 忘記在函式或屬性上加入 ` null/
類別屬性未初始化 strictPropertyInitialization 會阻止未賦值的屬性。 在宣告時給予預設值,或在建構子裡完整賦值;若屬性確實可延遲初始化,可加上 ! 非空斷言(慎用)。
函式參數型別不匹配 strictFunctionTypes 讓函式參數的協變檢查更嚴格。 使用泛型或 overload,讓型別更貼近實際呼叫情境。
this 失去型別 在普通函式中使用 this,編譯器無法推斷。 改用箭頭函式(自動綁定 this)或 在函式簽名中明確宣告 this 型別

最佳實踐清單

  1. noImplicitAny 開始:先解決隱式 any,再逐步開啟其他子選項。
  2. 使用 --watch 搭配 IDE:即時看到錯誤,避免一次性大量修正造成的焦慮。
  3. 建立型別工具函式:例如 assertIsString(value: unknown): asserts value is string,可以在執行時保證型別安全。
  4. 在第三方庫上使用 @types:若缺少型別宣告,先自行撰寫或使用 declare module 包裝。
  5. CI 中加入 tsc --noEmit:確保每次提交都通過嚴格檢查,防止錯誤進入主分支。

實際應用場景

1. 大型前端單頁應用(SPA)

在 React 或 Vue 專案中,資料流經常在多層元件間傳遞。開啟 strictNullChecks 能讓你在 propsstatecontext 中明確標示哪些屬性可能為 null,避免渲染時的空指標例外。

type UserCardProps = {
  name: string;
  avatarUrl?: string | null; // 可能為 null
};

export const UserCard: React.FC<UserCardProps> = ({ name, avatarUrl }) => (
  <div>
    <h2>{name}</h2>
    {avatarUrl ? <img src={avatarUrl} alt={name} /> : <DefaultAvatar />}
  </div>
);

2. Node.js 後端服務

在 Express 中,請求物件的屬性(例如 req.body)往往是 any。透過 noImplicitAny 和自訂型別(interface RequestBody { id: number; }),可以在路由處理函式裡直接取得型別提示,減少手寫校驗的負擔。

import { Request, Response } from 'express';

interface CreateUserBody {
  name: string;
  email: string;
}

app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response) => {
  const { name, email } = req.body; // 完全受型別保護
  // ...
});

3. 套件開發(Library)

若你正為 npm 發布 TypeScript 套件,嚴格模式是品質保證。使用者在安裝你的套件後,若開啟 strict,他們的編譯器會直接檢測到不相容的型別,提升套件的可用性與信任度。

// package.json 中的 "types"
{
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc -p tsconfig.json"
  }
}

總結

  • compilerOptions.strict 是 TypeScript 提供的 全方位型別安全總開關,一次啟用即能開啟多項嚴格檢查。
  • 開啟後,你會在開發初期看到大量編譯錯誤,這是 早期捕捉問題、提升程式碼品質 的好機會。
  • 透過 分步啟用、結合 IDE 錯誤提示、在 CI 中加入檢查,可以平滑地將既有專案遷移至嚴格模式。
  • 在實務上,strict 能防止常見的 null/undefined 錯誤、隱式 any、未初始化屬性等問題,特別適用於 大型前端框架、Node.js 後端服務與套件開發

建議:從 noImplicitAny 開始逐步加強,最終將 strict: true 作為專案的預設設定,讓型別安全成為團隊開發文化的一部分。

祝你在 TypeScript 的型別世界裡玩得開心,寫出更安全、更可靠的程式碼! 🚀