TypeScript 宣告檔案 (Declaration Files)

宣告檔案 (Declaration Files) 是副檔名為 .d.ts 的檔案,用來為 JavaScript 程式碼提供型別資訊。它們讓 TypeScript 能夠理解和檢查 JavaScript 函式庫的型別。

為什麼需要宣告檔案?

許多流行的 JavaScript 函式庫 (如 jQuery、Lodash) 是用純 JavaScript 寫的,沒有型別資訊。宣告檔案就像是這些函式庫的「型別說明書」,讓 TypeScript 知道如何使用它們。

// 沒有宣告檔案,TypeScript 不知道 $ 是什麼
// 會報錯:Cannot find name '$'
$('.button').click();

// 有了宣告檔案,就能正確使用並獲得型別提示
$('.button').click(function (event) {
  console.log(event.target);
});

安裝型別定義

@types 套件

大多數流行的函式庫都有社群維護的型別定義,放在 DefinitelyTyped 倉庫中。使用 npm 安裝:

# 安裝 jQuery 的型別定義
npm install --save-dev @types/jquery

# 安裝 Lodash 的型別定義
npm install --save-dev @types/lodash

# 安裝 Node.js 的型別定義
npm install --save-dev @types/node

安裝後,TypeScript 會自動找到並使用這些型別定義。

內建型別定義

有些套件自帶型別定義,不需要額外安裝:

# axios 自帶型別定義
npm install axios

# date-fns 自帶型別定義
npm install date-fns

撰寫宣告檔案

基本語法

宣告檔案使用 declare 關鍵字來宣告型別,而不是實作:

// globals.d.ts

// 宣告全域變數
declare const VERSION: string;
declare let DEBUG: boolean;

// 宣告全域函式
declare function greet(name: string): void;

// 宣告全域類別
declare class User {
  name: string;
  constructor(name: string);
  greet(): void;
}

宣告模組

// lodash.d.ts
declare module 'lodash' {
  export function chunk<T>(array: T[], size?: number): T[][];
  export function compact<T>(array: T[]): T[];
  export function concat<T>(...arrays: T[][]): T[];
  // ...更多函式
}

擴增現有模組

// express-extensions.d.ts
import { Request } from 'express';

declare module 'express' {
  interface Request {
    user?: {
      id: string;
      name: string;
      role: string;
    };
  }
}

宣告全域變數

當你使用的 JavaScript 在全域範圍定義了變數時:

// global.d.ts

// 簡單值
declare const API_URL: string;

// 物件
declare const config: {
  apiUrl: string;
  timeout: number;
};

// 函式
declare function analytics(event: string, data?: object): void;

// 使用全域命名空間
declare namespace MyApp {
  const version: string;
  function init(): void;
  interface Options {
    debug: boolean;
  }
}

宣告類別

// my-class.d.ts
declare class MyClass {
  // 屬性
  readonly id: number;
  name: string;
  private secret: string;

  // 建構子
  constructor(name: string);

  // 方法
  greet(): string;
  setName(name: string): void;

  // 靜態成員
  static create(name: string): MyClass;
  static readonly VERSION: string;
}

宣告函式

// functions.d.ts

// 簡單函式
declare function greet(name: string): string;

// 函式重載
declare function format(value: string): string;
declare function format(value: number): string;
declare function format(value: Date): string;

// 可選參數
declare function log(message: string, level?: string): void;

// 回呼函式
declare function fetchData(url: string, callback: (error: Error | null, data: any) => void): void;

宣告介面和型別

// types.d.ts

// 介面
declare interface User {
  id: number;
  name: string;
  email?: string;
}

// 型別別名
declare type Status = 'pending' | 'active' | 'completed';

// 泛型介面
declare interface Response<T> {
  data: T;
  status: number;
  message: string;
}

環境宣告 (Ambient Declarations)

擴增 Window 物件

// window.d.ts
interface Window {
  myApp: {
    version: string;
    init(): void;
  };
  dataLayer: any[];
  gtag: (...args: any[]) => void;
}

擴充全域物件

// global.d.ts
declare global {
  interface Array<T> {
    customMethod(): T[];
  }

  interface String {
    customFormat(): string;
  }
}

export {}; // 確保這是一個模組

宣告檔案的結構

專案結構

project/
├── src/
│   ├── index.ts
│   └── utils.ts
├── types/
│   ├── global.d.ts
│   ├── my-library.d.ts
│   └── custom.d.ts
├── tsconfig.json
└── package.json

tsconfig.json 設定

{
  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"],
    "types": ["node", "jest"]
  },
  "include": ["src/**/*", "types/**/*"]
}

發佈型別定義

套件內含型別

package.json 中指定型別檔案:

{
  "name": "my-library",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"]
}

產生宣告檔案

tsconfig.json 中啟用:

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./dist/types",
    "emitDeclarationOnly": false
  }
}

實用範例

jQuery 宣告

// jquery.d.ts
declare namespace JQuery {
  interface EventObject {
    target: Element;
    type: string;
    preventDefault(): void;
  }
}

interface JQuery {
  click(handler: (event: JQuery.EventObject) => void): this;
  hide(): this;
  show(): this;
  val(): string;
  val(value: string): this;
}

declare function $(selector: string): JQuery;
declare function $(element: Element): JQuery;
declare function $(callback: () => void): JQuery;

環境變數

// env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    API_URL: string;
    SECRET_KEY: string;
  }
}

圖片和樣式模組

// assets.d.ts

// 圖片
declare module '*.png' {
  const value: string;
  export default value;
}

declare module '*.jpg' {
  const value: string;
  export default value;
}

declare module '*.svg' {
  const value: string;
  export default value;
}

// 樣式
declare module '*.css' {
  const classes: { [key: string]: string };
  export default classes;
}

declare module '*.scss' {
  const classes: { [key: string]: string };
  export default classes;
}

Vue 單檔案元件

// vue-shim.d.ts
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

常見問題

找不到模組的型別

// 如果沒有 @types 套件,可以自己宣告
declare module 'some-untyped-module' {
  export function someFunction(): void;
  export const someValue: string;
}

// 或者簡單地允許任何匯出
declare module 'some-untyped-module';

隱式 any

// tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "skipLibCheck": true
  }
}

總結

  • 宣告檔案 (.d.ts) 為 JavaScript 程式碼提供型別資訊
  • 使用 @types/* 套件安裝社群維護的型別定義
  • 使用 declare 關鍵字宣告型別
  • 可以擴充全域物件和現有模組
  • 套件可以內含型別定義或由社群維護