Skip to main content

宣告合併:讓你的型別定義更靈活

在 TypeScript 開發中,我們經常需要定義複雜的型別結構。有時候,我們會遇到需要將多個相同名稱的函式、介面或類別合併成一個的情況。TypeScript 提供了一個強大的特性叫做「宣告合併」(Declaration Merging),讓我們可以更靈活地組織和擴展型別定義。

什麼是宣告合併?

宣告合併是 TypeScript 的一個特性,當你定義了兩個或多個相同名字的函式、介面或類別時,TypeScript 會自動將它們合併成一個型別。這個特性讓我們可以將型別定義分散在多個地方,然後在編譯時自動合併。

函式的合併

函式合併是宣告合併中最常見的應用,也就是我們熟知的函式過載(Function Overloading)。

// 定義多個函式過載
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === "number") {
return Number(x.toString().split("").reverse().join(""));
} else if (typeof x === "string") {
return x.split("").reverse().join("");
}
}

// 使用範例
const numResult = reverse(123); // 回傳 321
const strResult = reverse("hello"); // 回傳 'olleh'

在上面的例子中,我們定義了兩個函式過載,分別處理 numberstring 類型的參數,最後提供實際的實作。TypeScript 會將這些宣告合併,提供正確的型別檢查。

介面的合併

介面合併是另一個強大的特性,它允許我們將多個同名介面的屬性合併到一個介面中。

基本屬性合併

interface Alarm {
price: number;
}

interface Alarm {
weight: number;
}

// 上述兩個介面會自動合併成:
interface Alarm {
price: number;
weight: number;
}

型別一致性要求

在介面合併時,相同屬性的型別必須保持一致

// ✅ 正確:型別一致
interface Alarm {
price: number;
}

interface Alarm {
price: number; // 型別相同,不會報錯
weight: number;
}

// ❌ 錯誤:型別不一致
interface Alarm {
price: number;
}

interface Alarm {
price: string; // 型別不一致,會報錯
weight: number;
}

// 編譯錯誤:
// error TS2403: Subsequent variable declarations must have the same type.
// Variable 'price' must be of type 'number', but here has type 'string'.

方法合併

介面中的方法合併與函式合併的規則相同:

interface Alarm {
price: number;
alert(s: string): string;
}

interface Alarm {
weight: number;
alert(s: string, n: number): string;
}

// 合併後的介面:
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}

實際應用場景

1. 擴展第三方函式庫

// 擴展 jQuery 的型別定義
interface JQuery {
tooltip(): JQuery;
tooltip(options: any): JQuery;
}

// 在其他地方繼續擴展
interface JQuery {
modal(): JQuery;
modal(options: any): JQuery;
}

2. 模組化型別定義

// user.ts
interface User {
id: number;
name: string;
}

// user-permissions.ts
interface User {
permissions: string[];
isAdmin: boolean;
}

// user-profile.ts
interface User {
email: string;
avatar?: string;
}

// 最終合併的 User 介面包含所有屬性

3. 配置物件擴展

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

// 在開發環境中擴展
interface Config {
debug: boolean;
logLevel: "info" | "warn" | "error";
}

// 在生產環境中擴展
interface Config {
enableAnalytics: boolean;
cacheSize: number;
}

注意事項

  1. 型別一致性:合併的屬性型別必須完全一致
  2. 命名空間:宣告合併只在全域範圍或模組內部有效
  3. 執行順序:合併的順序會影響最終的型別定義
  4. 可讀性:過度使用可能會讓程式碼難以理解

總結

宣告合併是 TypeScript 中一個非常實用的特性,它讓我們可以:

  • 模組化型別定義:將複雜的型別定義分散到多個檔案中
  • 擴展第三方函式庫:為現有的函式庫添加型別定義
  • 保持程式碼組織性:避免單一檔案過於龐大

適當地使用宣告合併可以讓你的 TypeScript 程式碼更加靈活和可維護。不過也要注意不要過度使用,以免影響程式碼的可讀性。

希望這篇文章能幫助你更好地理解和應用 TypeScript 的宣告合併特性!