TypeScript 宣告檔案 (Declaration Files)

你一定有過這個經驗:下載了一個 npm 套件,import 進來時 TS 卻對著你大叫:

"Could not find a declaration file for module 'xxx'."

這是因為大多數的 npm 套件還是用 JavaScript 寫的,TypeScript 看不懂它們。 這時候我們需要一本說明書,告訴 TypeScript 這些 JS 程式碼到底長什麼樣子。

這本說明書就是 宣告檔案 (.d.ts)

.d.ts 是什麼?

.d.ts 檔案只包含型別定義不包含任何可執行的程式碼。它有點像 C 語言的 Header file (.h)。

例子:jQuery 的說明書

假設我們有一個全域變數 $ (jQuery),但 TS 不認識它。 我們可以寫一個 global.d.ts 來「宣告」它的存在:

// global.d.ts
// 告訴 TS:放心,執行環境裡會有一個叫 $ 的東西
declare var $: (selector: string) => any;

這樣 TS 就不會報錯了。

如何取得 .d.ts

1. 套件自帶 (Bundled Types)

很多現代套件 (如 axios, vue, date-fns) 已經是用 TypeScript 寫的,或者作者在發布時順便打包了 .d.ts 檔案。 這種最爽,安裝完直接用,什麼都不用設定。

2. DefinitelyTyped (@types/xxx)

對於那些沒有附帶型別的 JS 套件 (如 express, lodash, jquery),社群好心人幫忙寫了說明書,並放在 DefinitelyTyped 這個專案裡。 你需要手動安裝它:

npm install --save-dev @types/express
npm install --save-dev @types/lodash

3. 自己寫 (Custom Declaration)

如果連 @types 都沒有,那你只能自己寫了。 在專案根目錄建立一個 types/my-module.d.ts

// 告訴 TS 這個模組存在,甚至可以使用 lazy 宣告法:
declare module 'my-weird-library'; // 把它當成 any

// 如果你只是想讓編譯器閉嘴,不介意沒有詳細的型別提示,可以這樣寫
// 這樣一來,import 該模組時,它會被視為 any 型別

常見的 declare 語法

你會在 .d.ts 檔裡看到很多 declare 開頭的東西:

// 宣告全域變數
declare const API_URL: string;

// 宣告全域函式
declare function alert(message: string): void;

// 宣告一個模組 (例如你在 import css 檔時會用到)
declare module '*.css' {
  const content: { [className: string]: string };
  export default content;
}

Module Augmentation (模組擴充)

有的時候,我們不是要重新宣告一個套件,而是要擴充它現有的型別。 例如:我想在 ExpressRequest 物件上加一個 user 屬性。

我們不需要去修改 node_modules 裡的檔案,而是使用 Augmentation

// types/express.d.ts

// 1. 必須先 import 該模組,確保它被視為模組擴充
import 'express';

// 2. 使用 declare module 擴充它
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      username: string;
      role: 'admin' | 'user';
    };
  }
}

這樣做之後,你的程式碼就可以愉快地使用 req.user 了!

Global Augmentation (全域擴充)

如果你想在 window 物件上加東西,這叫做全域擴充。 在模組化 (有 import/export) 的檔案中,你需要把宣告包在 declare global 裡。

// types/window.d.ts

export {}; // 確保這檔案被視為模組 (若檔案已有其他 import/export 則不需這行)

declare global {
  interface Window {
    __REDUX_DEVTOOLS_EXTENSION__: any;
    analytics: {
      track: (event: string) => void;
    };
  }
}

現在你是可以在任何地方使用 window.analytics.track('click') 了。

假如我要發布套件?

如果你在寫一個 npm 套件,記得要在 package.json 中告訴使用者你的型別檔案在哪裡:

{
  "name": "my-awesome-lib",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts"
}
  • types (或 typings) 欄位指向你的入口 .d.ts 檔案。

總結

  • .d.ts 是 JS 和 TS 之間的橋樑。
  • Module Augmentation 讓你可以在不修改原始碼的情況下,為第三方套件「補丁」新的型別屬性。
  • Global Augmentation 讓你可以安全地擴充 windowglobal 物件。