TypeScript 基礎概念:型別檔(.d.ts / DefinitelyTyped)
簡介
在 JavaScript 的世界裡,程式碼本身不帶任何型別資訊,這讓 IDE 的自動完成、靜態檢查與大型專案的維護變得相當困難。TypeScript 透過 型別系統 為 JavaScript 加上靜態型別,進而提升開發效率與程式品質。
然而,並非所有的第三方套件都有原生的 TypeScript 型別定義。這時 型別檔(.d.ts) 就派上用場:它是一份只描述型別的檔案,讓 TypeScript 編譯器在編譯時能正確推斷、檢查外部程式庫的 API。
如果你想在專案中無縫使用 npm 上的眾多 JavaScript 套件,只要善用 DefinitelyTyped(社群維護的型別檔集合)與自訂 .d.ts,就能享受到完整的型別安全與開發體驗。
核心概念
1. .d.ts 檔案的角色
- 聲明檔:只描述「什麼」而不實作「如何」;不會產生任何 JavaScript 輸出。
- 全域或模組:可以在全域環境裡宣告變數、介面,也可以以 ES Module / CommonJS 的方式匯出型別。
- 對應 JavaScript 程式庫:讓 TypeScript 能在編譯階段理解純 JavaScript 套件的 API 結構。
2. DefinitelyTyped
- 什麼是 DefinitelyTyped:GitHub 上的開源專案,收錄了上萬個流行 JavaScript 套件的型別定義。
- 安裝方式:使用 npm 或 Yarn 安裝
@types/<套件名稱>,例如npm i -D @types/lodash。 - 版本對應:型別檔的版本會與套件的主要版本保持同步,確保型別定義不會因套件升級而失效。
3. 手寫 .d.ts 的基本語法
| 關鍵字 | 說明 |
|---|---|
declare |
用於聲明全域變數、函式、介面或模組。 |
namespace |
組織相關型別,常見於舊式套件(如 jQuery)。 |
export / export = |
匯出型別給外部模組使用。 |
/// <reference types="..." /> |
引入其他型別檔的依賴。 |
程式碼範例
範例 1:為純 JavaScript 函式庫手寫簡易型別檔
假設有一個 utils.js,只提供兩個函式:
// utils.js
function add(a, b) {
return a + b;
}
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = { add, greet };
我們可以為它建立 utils.d.ts:
// utils.d.ts
declare module "utils" {
/** 計算兩個數字的加總 */
export function add(a: number, b: number): number;
/** 產生問候字串 */
export function greet(name: string): string;
}
使用時:
import { add, greet } from "utils";
const sum = add(3, 5); // sum 的型別被推斷為 number
console.log(greet("Alice")); // 正確取得字串回傳值
重點:只需要描述函式簽名與回傳型別,實作細節仍由原始的
utils.js提供。
範例 2:使用 DefinitelyTyped 為 lodash 加上型別
npm i lodash # 安裝原始套件
npm i -D @types/lodash # 安裝型別定義
在 TypeScript 檔案中直接使用:
import _ from "lodash";
const numbers = [1, 2, 3, 4];
const doubled = _.map(numbers, n => n * 2); // doubled 被推斷為 number[]
透過 @types/lodash,IDE 能即時顯示 _.map 的參數型別與回傳型別,減少錯誤的可能性。
範例 3:全域宣告第三方 CDN 載入的庫(例如 moment.js)
若專案直接在 HTML 中引入 CDN:
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.min.js"></script>
我們需要在 TypeScript 中告訴編譯器 moment 是全域變數:
// moment-global.d.ts
declare const moment: typeof import("moment");
export = moment; // 若想以 CommonJS 方式匯入也可
使用方式:
// app.ts
const now = moment(); // now 的型別為 Moment
console.log(now.format("YYYY-MM-DD"));
範例 4:擴充既有型別(Declaration Merging)
假設想為 express 的 Request 介面加入自訂屬性 user:
// express.d.ts
import "express";
declare module "express-serve-static-core" {
interface Request {
/** 已驗證的使用者資訊 */
user?: {
id: string;
role: "admin" | "member";
};
}
}
之後在路由處理函式中即可安全存取:
import { Request, Response } from "express";
app.get("/profile", (req: Request, res: Response) => {
if (req.user) {
res.send(`Welcome, ${req.user.id}`);
} else {
res.status(401).send("Unauthenticated");
}
});
技巧:利用 宣告合併(Declaration Merging)可以不破壞原始套件的型別,同時加入專案特有的資訊。
範例 5:為非模組化的舊版程式庫(如 jQuery)寫自訂型別
雖然 DefinitelyTyped 已提供 @types/jquery,但若你只需要部份功能,可以自行建立:
// jquery-lite.d.ts
declare const $: JQueryStatic;
interface JQueryStatic {
/** 取得第一個符合條件的元素 */
(selector: string): JQuery;
/** 執行 AJAX 請求 */
ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR;
// ... 只列出你需要的 API
}
在程式中:
$("#btn").on("click", () => {
$.ajax("/api/data", {
method: "GET",
success: (data) => console.log(data),
});
});
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記安裝對應的 @types |
編譯時會出現 “Cannot find module …” 錯誤 | npm i -D @types/<套件>,或自行建立 .d.ts |
| 型別檔版本與套件不匹配 | 可能因 API 變更導致型別錯誤 | 確認 @types 版本的 major 與套件相同,必要時升級或降版 |
| 全域宣告衝突 | 多個 .d.ts 同時宣告同名全域變數 |
使用 namespace 或 module augmentation 限定範圍 |
過度使用 any |
失去型別檢查的意義 | 盡量使用具體型別或 unknown,必要時再逐步加上斷言 |
忘記 declare |
直接寫函式或變數會產生實作,導致重複編譯錯誤 | 在 .d.ts 中一定要用 declare 開頭聲明 |
最佳實踐
- 優先使用 DefinitelyTyped:社群維護的型別檔最完整、更新最快。
- 自訂型別檔時保持最小化:只宣告你實際使用的 API,避免維護負擔。
- 使用
export =與export default匹配原始套件的匯出方式,防止 import 錯誤。 - 將自訂型別檔放在
src/types或typings目錄,並在tsconfig.json加入typeRoots設定。 - 啟用
noImplicitAny、strictNullChecks,在開發階段就捕捉型別遺漏。
// tsconfig.json 片段
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"typeRoots": ["./node_modules/@types", "./src/types"]
}
}
實際應用場景
- 前端框架整合:在 React、Vue 或 Angular 專案中,引入第三方 UI 庫(如
antd、bootstrap)時,型別檔確保組件屬性、事件的正確性。 - Node.js 後端:使用純 JavaScript 的資料庫驅動(如
mongodb、redis)時,透過@types取得 query 參數與回傳結果的型別,減少 runtime 錯誤。 - 微服務與共享套件:團隊內部發布的共用工具庫可以只提供
.d.ts,讓其他服務在不需要重新編譯 JavaScript 程式碼的情況下直接使用型別。 - 遺留系統升級:老舊的 jQuery / underscore 程式碼逐步遷移至 TypeScript,先寫
.d.ts讓編譯器認識舊程式,再逐步改寫為 ES6+ 模組。
總結
- 型別檔 (
.d.ts) 是 TypeScript 與純 JavaScript 生態系之間的橋樑,讓開發者在使用第三方套件時仍能享有完整的型別安全。 - DefinitelyTyped 提供了最廣泛、最即時的型別定義,只要安裝相對應的
@types套件,即可即刻獲得 IntelliSense 與編譯檢查。 - 當社群型別檔不足時,自行撰寫
.d.ts並以declare、export、module augmentation等語法描述 API,即可快速補足缺口。 - 避免常見陷阱(版本不符、全域衝突、過度使用
any),並遵守最佳實踐(嚴格編譯選項、統一型別根目錄),可讓專案在規模擴大時仍保持可維護性。
掌握型別檔的使用與管理,不僅能提升開發效率,更能在大型前後端專案中降低錯誤率、提升程式碼品質。希望本篇文章能幫助你在 TypeScript 的旅程中,順利利用 .d.ts 與 DefinitelyTyped,寫出更安全、更可靠的程式碼!