TypeScript – 型別宣告與整合:自訂 .d.ts 型別檔
簡介
在大型前端或 Node.js 專案中,我們常會使用第三方函式庫、原生瀏覽器 API,甚至是自家寫的 JavaScript 模組。這些程式碼本身 沒有 TypeScript 的型別資訊,如果直接在 TypeScript 檔案中引用,編譯器會把它視為 any,失去靜態檢查與 IntelliSense 的好處,容易產生執行時錯誤。
自訂 .d.ts(宣告檔) 正是為了解決這個問題:它讓我們能夠在不改動原始 JavaScript 程式碼的前提下,為其補上完整的型別描述。透過宣告檔,開發者可以:
- 讓編譯器正確推斷參數、回傳值與屬性型別
- 在 IDE 中取得自動完成與文件提示
- 以型別安全的方式整合多種程式庫,降低維護成本
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步掌握自訂 .d.ts 的技巧,並提供幾個在實務專案中常見的應用場景。
核心概念
1. 為什麼要使用 .d.ts?
- 第三方 JavaScript 套件:許多套件尚未提供官方型別檔,或型別檔不完整。
- 自家寫的純 JavaScript 模組:在遷移到 TypeScript 時,先寫宣告檔可以讓遷移過程平滑。
- 全域變數或瀏覽器 API:有時會使用到非標準或實驗性的 API,需要自行宣告。
重點:
.d.ts只會提供型別資訊,不會產生任何 JavaScript 程式碼。
2. 基本語法概覽
| 關鍵字 | 說明 |
|---|---|
declare |
告訴編譯器「此處僅為型別宣告」;常用於函式、變數、模組等。 |
namespace |
為全域變數或舊式模組建立命名空間,避免衝突。 |
export / export = |
讓宣告檔支援 ES 模組或 CommonJS 匯出方式。 |
/// <reference path="..." /> |
手動引用其他宣告檔(較少使用,因為 tsconfig.json 已能自動解析)。 |
3. 常見的宣告檔類型
| 類型 | 範例檔名 | 用途 |
|---|---|---|
| 全域 (Global) | globals.d.ts |
為全域變數、介面、函式提供型別。 |
| 模組 (Module) | my-lib.d.ts |
為 import / require 的套件提供型別。 |
| 擴充 (Augmentation) | vue-shim.d.ts |
為現有套件或全域介面加入額外屬性。 |
程式碼範例
以下示範 5 個實務中常見的 .d.ts 撰寫方式,從最簡單的全域變數到完整的模組宣告皆有涵蓋。
範例 1️⃣:全域變數與函式
/* globals.d.ts */
declare const API_BASE_URL: string;
/**
* 取得目前使用者的 JWT token
* @returns token 字串,若未登入則回傳 null
*/
declare function getAuthToken(): string | null;
說明:
declare const讓全域常數API_BASE_URL具備string型別;declare function為全域函式提供參數與回傳型別。
範例 2️⃣:為純 JavaScript 模組寫模組宣告(CommonJS)
假設有一個 logger.js:
// logger.js
module.exports = {
log: (msg) => console.log(`[LOG] ${msg}`),
error: (msg) => console.error(`[ERR] ${msg}`)
};
對應的 .d.ts:
/* logger.d.ts */
declare module "logger" {
export function log(msg: string): void;
export function error(msg: string): void;
}
說明:使用
declare module "logger"讓 TypeScript 能以 ES6import語法引用logger,同時保留 CommonJS 的匯出結構。
範例 3️⃣:ES Module 宣告(預設匯出)
/* utils.d.ts */
declare module "utils" {
export default function formatDate(date: Date, format?: string): string;
}
使用方式:
import formatDate from "utils";
const today = formatDate(new Date(), "YYYY/MM/DD");
重點:
export default必須寫在declare module內,才能正確對應 ES6 預設匯出。
範例 4️⃣:擴充第三方套件(模組合併)
假設使用 express,想為 Request 介面加上自訂屬性 userId:
/* express-augmentation.d.ts */
import "express";
declare module "express-serve-static-core" {
interface Request {
/** 目前登入使用者的 ID */
userId?: string;
}
}
說明:先
import "express"讓 TypeScript 載入原始型別,接著使用declare module針對其子模組express-serve-static-core進行介面合併(augmentation),不會破壞原本的型別定義。
範例 5️⃣:宣告非標準瀏覽器 API(全域介面)
/* web-apis.d.ts */
interface Navigator {
/** 判斷是否支援 WebXR */
readonly xr?: any;
}
/**
* 啟動 WebXR Session
* @param mode Session 類型,預設為 'immersive-vr'
*/
declare function startXRSession(mode?: "immersive-vr" | "immersive-ar"): Promise<void>;
使用情境:在開發 VR/AR 網頁時,瀏覽器尚未正式支援
navigator.xr,我們自行宣告以取得型別提示。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記加 declare |
直接寫 function foo(){} 會產生 JavaScript,導致編譯錯誤。 |
必須使用 declare function foo(...): ...。 |
| 宣告檔路徑未被辨識 | tsconfig.json 未包含 .d.ts 所在目錄。 |
在 tsconfig.json 的 include 中加入 **/*.d.ts 或指定 typeRoots。 |
| 重複宣告衝突 | 同一名稱在多個檔案中重複宣告,會產生「Duplicate identifier」錯誤。 | 利用 命名空間 或 模組合併,或將宣告集中於同一檔案。 |
使用 any 過度 |
為了快速解決錯誤而把所有型別寫成 any,失去 TypeScript 的優勢。 |
盡量使用具體型別或 泛型,必要時才使用 unknown 再做類型守衛。 |
| 忘記匯出 | 宣告檔內的介面或型別未 export,外部無法引用。 |
在模組宣告裡使用 export interface …、export type …。 |
最佳實踐
- 分層管理:全域宣告放在
globals.d.ts,第三方套件放在types/目錄,專案自訂模組放在src/types/。 - 保持同步:當原始 JavaScript 檔案變動時,立即更新對應的
.d.ts,避免型別不一致。 - 使用
--noEmit:在純型別檔案的專案中,設定tsc --noEmit,只做型別檢查,避免產生不必要的 JS。 - 結合 JSDoc:在
.d.ts中加入 JSDoc 註解,可在 IDE 中顯示說明,提升開發者體驗。 - 測試型別:利用
tsd或dtslint這類工具撰寫型別測試,確保宣告檔的正確性。
實際應用場景
1. 專案遷移:從 JavaScript 到 TypeScript
情境:大型專案已有 10,000 行 JavaScript,直接改寫成本過高。
做法:先為每個已有模組寫對應的.d.ts,讓新撰寫的 TypeScript 檔案可以安全引用。逐步將核心模組改寫為 TypeScript,同時保留原始 JavaScript。
2. 使用未提供型別的第三方套件
情境:使用
chart.js的舊版套件,官方未提供型別。
解決:在types/chartjs.d.ts中宣告主要的構造函式與介面,讓整個專案在使用new Chart(...)時得到完整的型別提示。
3. 前端框架自訂插件
情境:在 Vue 3 中開發自訂指令
v-debounce,想讓 TypeScript 認識指令的參數型別。
做法:在vue-shim.d.ts中擴充ComponentCustomProperties,加入v-debounce的型別描述,讓組件內使用指令時不會出現any警告。
4. 企業內部共用函式庫
情境:公司內部有一套共用的
api-client,以純 JavaScript 發布於私有 npm。
做法:在每個使用此套件的專案中加入api-client.d.ts,或在私有 npm 中同時發布@types/api-client,確保所有開發者都有一致的型別資訊。
總結
自訂 .d.ts 型別檔是 橋接 JavaScript 與 TypeScript 的關鍵工具。透過正確的宣告,我們可以:
- 為第三方或自家 JavaScript 套件提供完整的型別資訊
- 在 IDE 中獲得自動完成與文件提示,提高開發效率
- 在大型或遷移專案中保持型別安全,減少執行時錯誤
本文從概念、語法、實作範例、常見陷阱與最佳實踐,以及實務應用場景逐層說明,期望讀者能在 1500 字左右的篇幅 中快速掌握自訂 .d.ts 的要領。只要遵循「分層管理、保持同步、加入文件註解」的原則,便能在日常開發中穩健地使用 TypeScript,讓程式碼更加可讀、可維護、且錯誤率更低。
最後提醒:型別宣告不是一次寫好就永遠不變的文件;隨著程式碼演進,持續更新
.d.ts才能真正發揮 TypeScript 的力量。祝你在型別世界裡玩得開心!