本文 AI 產出,尚未審核

TypeScript 課程 – 型別相容性與型別系統(Type Compatibility)

主題:Excess Property Check(多餘屬性檢查)


簡介

在日常開發中,我們常會把 物件字面量 直接傳給函式或介面(interface)作為參數。
TypeScript 為了保護開發者不小心寫錯屬性名稱,提供了 Excess Property Check(簡稱 EPC)機制。
這項檢查會在 直接使用物件字面量 時,驗證它是否包含了目標型別未定義的屬性,若有則拋出編譯錯誤。

EPC 並不是在所有情況下都會觸發,只有在「物件字面量」被 直接 指派或傳遞給 結構型別 時才會生效。
了解這個機制不僅能避免因筆誤造成的錯誤,也能幫助我們寫出更嚴謹、可維護的程式碼。


核心概念

1. 為什麼會有「多餘屬性」的概念?

TypeScript 採用 結構型別系統(structural type system),只要物件的形狀(屬性名稱與型別)符合目標型別,就視為相容。
舉例來說:

interface Person {
  name: string;
  age: number;
}

只要一個物件至少有 name(字串)與 age(數字)這兩個屬性,就能指派給 Person
然而,若在物件字面量中不小心多寫了 gender,在純結構相容的情況下仍會通過,這會讓開發者在執行時才發現錯誤。

2. EPC 的觸發時機

場景 是否觸發 EPC
直接 把物件字面量傳給函式或指派給變數
把物件字面量先 儲存 在變數,再指派給型別 ❌(除非使用 as const 或斷言)
使用 型別斷言 (as) 或 泛型 ❌(斷言會跳過檢查)
物件來源是 函式回傳Spread、或 解構 ❌(視為非字面量)

簡單理解:只要編譯器能「看到」這是一個字面量,就會執行 EPC。

3. 基本範例:EPC 正常運作

interface Config {
  url: string;
  timeout: number;
}

// ✅ 正確:屬性完全吻合
const cfg1: Config = { url: "https://api.example.com", timeout: 5000 };

// ❌ 錯誤:多了未定義的 `retries` 屬性
const cfg2: Config = { url: "https://api.example.com", timeout: 5000, retries: 3 };
//               ~~~~~~~~~~~~~~~~  ^^^^^^^^^^^^^^^^^^^^^^^^^^  多餘屬性

編譯器會提示:

Object literal may only specify known properties, and 'retries' does not exist in type 'Config'.

4. 變通寫法:使用型別斷言或變數儲存

如果確實需要傳入額外屬性(例如暫時的測試資料),可以先把物件存到變數,或使用型別斷言:

// 方式一:先存變數(此時不會觸發 EPC)
const temp = { url: "https://api.example.com", timeout: 5000, retries: 3 };
const cfg3: Config = temp; // ✅ 合法,因為 temp 已不是字面量

// 方式二:型別斷言(告訴編譯器「我知道」這個物件符合 Config)
const cfg4 = { url: "https://api.example.com", timeout: 5000, retries: 3 } as Config;

注意:使用斷言會 關閉 EPC,若屬性真的寫錯,編譯器不會提醒,請謹慎使用。

5. --exactOptionalPropertyTypes 與 EPC

從 TypeScript 4.4 起,新增了 --exactOptionalPropertyTypes 編譯選項,讓 可選屬性?)也必須嚴格符合型別。
結合 EPC,能更精細地控制「可選」與「多餘」屬性的檢查。

interface Options {
  verbose?: boolean;
}

// 正常情況下,以下寫法不會報錯(因為多餘屬性不在字面量檢查範圍)
// const opt: Options = { verbose: true, debug: false }; // ❌ 仍會報錯

// 開啟 exactOptionalPropertyTypes 後,`debug` 會被視為多餘屬性

6. 使用 Index Signature 時的行為

如果介面使用 索引簽名(index signature),EPC 會被「寬鬆」處理:

interface Dictionary {
  [key: string]: string;
}

// 任意屬性皆被允許,EPC 不會報錯
const dict: Dictionary = { hello: "world", foo: "bar", number: "123" };

因為索引簽名表明「允許任意鍵」,編譯器不會再檢查「多餘」鍵。


常見陷阱與最佳實踐

陷阱 說明 解決方案
誤以為所有物件都會被 EPC 檢查 只有字面量會被檢查,變數或函式回傳的物件不會。 若要保證安全,盡量直接在呼叫處使用字面量,或使用 as const 斷言保留字面量資訊。
過度使用型別斷言 斷言會跳過 EPC,導致錯誤潛伏。 只在確定多餘屬性是有意為之時才使用,平時避免使用 as any
忽視可選屬性 可選屬性若寫錯,EPC 仍會提示,但若開啟 exactOptionalPropertyTypes,檢查更嚴格。 tsconfig.json 中加入 "exactOptionalPropertyTypes": true,提升檢查力度。
在大型物件上直接使用字面量 大型物件字面量如果多了幾個不相關屬性,錯誤訊息可能不易定位。 使用 介面拆分PartialPick 來限定必要屬性範圍。
忽略 Spread 產生的「非字面量」 ...obj 會讓檢查失效,導致不易發現多餘屬性。 若需要檢查,先使用 as const手動列出屬性

最佳實踐

  1. 盡量在函式呼叫端使用字面量:讓 EPC 自然發揮作用。
  2. 開啟 exactOptionalPropertyTypes:提升對可選屬性的檢查。
  3. 利用 as const:保留字面量的「只讀」資訊,讓型別推斷更精確。
  4. 針對大型設定物件使用 Partial<T>:只允許部分屬性,避免一次性寫入過多屬性。
  5. 在測試或臨時資料中,使用變數暫存:避免在正式程式碼裡使用斷言跳過檢查。

實際應用場景

1. 前端 UI 元件的 Props 定義

interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

// 正確使用
const btn1 = <MyButton label="送出" onClick={handleSubmit} />;

// 錯誤:多寫了 `color`,EPC 會提醒
const btn2 = <MyButton label="送出" onClick={handleSubmit} color="red" />;
//                     ~~~~~~~  ^^^^^^^  多餘屬性

EPC 幫助 UI 團隊在編譯階段即時捕捉錯字或不支援的屬性。

2. API 請求的參數物件

interface SearchParams {
  query: string;
  page?: number;
  limit?: number;
}

function fetchSearch(params: SearchParams) {
  // 內部實作省略
}

// 正確呼叫
fetchSearch({ query: "TypeScript", page: 2 });

// 錯誤:多了 `sortBy`,EPC 會報錯
fetchSearch({ query: "TypeScript", page: 2, sortBy: "date" });

在與後端溝通時,EPC 能避免因多餘參數導致的 API 失敗或安全漏洞。

3. 設定檔的型別驗證

interface AppConfig {
  apiUrl: string;
  retryCount: number;
  logLevel?: "debug" | "info" | "warn" | "error";
}

// 從 JSON 讀取設定(假設已經解析為 any)
const rawConfig: any = readConfigFile(); // 可能包含多餘屬性

// 使用型別斷言前先做檢查
const config: AppConfig = rawConfig; // ❌ 會在開發環境觸發 EPC

rawConfig 來自外部來源,建議使用 zodio-ts 等執行時驗證庫,配合 EPC 形成雙層防護。


總結

  • Excess Property Check 是 TypeScript 為了保護開發者不小心寫出「多餘屬性」而設計的編譯時檢查機制。
  • 只在 物件字面量直接指派或傳遞 時生效,其他情況(變數、斷言、Spread)則不會觸發。
  • 了解 EPC 的觸發條件與限制,能讓我們在 介面設計、函式參數、UI Props 等常見情境中,提前捕捉錯誤,提升程式碼品質。
  • 透過 as constexactOptionalPropertyTypes 以及 適當的型別斷言,可以在需要時靈活調整檢查的嚴格度。
  • 最佳實踐包括:盡量在呼叫端使用字面量、開啟嚴格選項、避免過度斷言、在大型設定物件上拆分介面或使用 Partial

掌握了 Excess Property Check 後,你的 TypeScript 程式碼將會更安全、更易於維護,也能在大型專案中減少因屬性拼寫錯誤或不必要的參數導致的 bug。祝你寫碼順利,玩得開心! 🎉