TypeScript 課程 ── 型別宣告與整合
主題:DefinitelyTyped 套件安裝(@types/)
簡介
在使用 TypeScript 開發前端或 Node.js 專案時,常會需要 第三方 JavaScript 套件(例如 lodash、moment、express 等)。這些套件本身是以純 JavaScript 撰寫,沒有內建的型別資訊,若直接在 TypeScript 中引用,編譯器將無法提供型別檢查與 IntelliSense,開發體驗會大打折扣。
為了解決這個問題,社群建立了 DefinitelyTyped 專案,集中管理超過 20,000 個套件的型別宣告檔 (*.d.ts)。開發者只要透過 npm 安裝對應的 @types/ 套件,即可讓 TypeScript 立即取得完整的型別資訊,提升開發效率與程式品質。
本篇文章將帶你 一步一步安裝與使用 @types/ 套件,並說明常見的陷阱與最佳實踐,讓你在實務專案中快速上手型別整合。
核心概念
1. DefinitelyTyped 與 @types/ 套件的關係
- DefinitelyTyped:一個開源倉庫,收錄各種 JavaScript 套件的型別宣告。
@types/:npm 上的發佈套件,名稱皆以@types/為前綴,代表「從 DefinitelyTyped 取得的型別定義」。- 安裝方式:
npm i -D @types/<package-name>(-D代表開發依賴)。
註:若套件本身已內建型別(如
react、axios),則不需要額外安裝@types/。
2. 安裝前的檢查
在安裝 @types/ 前,先確認套件的 版本相容性。
# 查看已安裝套件的版本
npm list lodash
# 取得對應的型別套件
npm view @types/lodash version
若 @types/lodash 的版本較舊,可能缺少新功能的型別,這時可以直接指定相同的主版本號:
npm i -D @types/lodash@^4.14.0
3. TypeScript 設定 (tsconfig.json)
tsconfig.json 中的 typeRoots 與 types 欄位可控制型別搜尋範圍。
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"strict": true,
"typeRoots": ["./node_modules/@types"], // 預設搜尋路徑
"types": ["node", "lodash"] // 只載入指定的型別套件
}
}
小技巧:若專案只需要少數型別,設定
types能減少編譯時間與衝突機會。
4. 程式碼範例
以下示範三個常見套件的型別安裝與使用方式。
範例 1:Lodash
npm i lodash
npm i -D @types/lodash
import _ from "lodash";
// 使用 _.chunk 時,IDE 會顯示完整的參數型別
const numbers = [1, 2, 3, 4, 5, 6];
const chunked = _.chunk(numbers, 2); // => [[1,2],[3,4],[5,6]]
範例 2:Moment.js
npm i moment
npm i -D @types/moment
import moment from "moment";
const now = moment(); // now: Moment
const formatted = now.format("YYYY-MM-DD"); // 取得字串型別
// 錯誤寫法:now.format(123); // 編譯時會直接報錯
範例 3:Express(Node.js)
npm i express
npm i -D @types/express
import express, { Request, Response } from "express";
const app = express();
app.get("/hello", (req: Request, res: Response) => {
// req.query 的型別會自動推斷為 string | string[] | undefined
const name = req.query.name ?? "World";
res.send(`Hello, ${name}`);
});
app.listen(3000, () => console.log("Server running on http://localhost:3000"));
範例 4:自訂全域型別(若找不到官方宣告)
# 假設使用的套件沒有 @types
npm i some-legacy-lib
// 在 src/types/global.d.ts 中自行宣告
declare module "some-legacy-lib" {
export function legacyFunc(param: string): number;
}
import { legacyFunc } from "some-legacy-lib";
const result = legacyFunc("test"); // result: number
範例 5:使用 skipLibCheck 減少型別衝突
{
"compilerOptions": {
"skipLibCheck": true // 跳過所有宣告檔的型別檢查
}
}
說明:在大型專案中,若某些
@types/套件的型別與自訂型別衝突,skipLibCheck能暫時讓編譯通過,但應盡量避免長期使用。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 安裝錯誤的版本 | @types/ 版本與實際套件不匹配,導致缺少或錯誤的型別 |
先確認套件的 major 版本,使用 npm view @types/<pkg> version,或加上 @<major> 指定 |
| 重複的全域型別 | 同時安裝多個相同名稱的型別套件(例如 @types/node 與某套件內建) |
只保留一個來源;若衝突,使用 typeRoots 或 paths 排除 |
忘記 -D |
把 @types/ 套件安裝成 production 依賴,造成最終產出檔案過大 |
使用 npm i -D @types/...,或在 package.json 中手動移到 devDependencies |
未設定 esModuleInterop |
某些 @types 需要 esModuleInterop:true 才能正確 import |
在 tsconfig.json 中加上 "esModuleInterop": true |
過度依賴 any |
為了快速跑通程式,直接把型別宣告為 any,失去型別保護 |
盡量使用 unknown 或自行擴充型別,保持 TypeScript 的安全性 |
最佳實踐
- 先搜尋官方型別:在 npm 上先查
package-name,若有@types/,直接安裝;若套件已內建型別,則不必額外安裝。 - 使用
--save-dev:所有@types/均應視為開發依賴。 - 定期更新:使用
npm outdated檢查型別套件的最新版本,避免因舊版型別導致錯誤。 - 在 CI 中加入型別檢查:確保每次提交都會執行
tsc --noEmit,防止型別衝突被忽略。
實際應用場景
- 前端 React 專案
- 安裝
@types/react、@types/react-dom,讓 JSX 元素得到完整型別支援。
- 安裝
- Node.js 後端 API
- 使用
express、mongoose,分別安裝@types/express、@types/mongoose,確保路由與資料模型的型別正確。
- 使用
- 測試框架
- Jest、Mocha 等測試工具需要
@types/jest、@types/mocha,才能在測試檔案中使用expect、describe等全域函式而不產生錯誤。
- Jest、Mocha 等測試工具需要
- 舊有 JavaScript 套件
- 當專案需要引入未提供型別的舊套件(例如
underscore的舊版),只要安裝@types/underscore,即能在 TypeScript 中安全使用。
- 當專案需要引入未提供型別的舊套件(例如
總結
DefinitelyTyped 為 TypeScript 生態系提供了 龐大的型別資源庫,只要透過 npm i -D @types/<package>,即可讓原本只有 JavaScript 的套件瞬間具備完整的型別支援。
本文從 安裝前的版本檢查、tsconfig.json 設定、實務範例,到 常見陷阱與最佳實踐,一步步說明了如何在專案中正確整合 @types/ 套件。
在日常開發中,養成定期更新型別套件、在 CI 中執行型別檢查 的好習慣,能最大化 TypeScript 的安全優勢,讓程式碼更可靠、更易維護。祝你在 TypeScript 的旅程中,玩得開心、寫得更好!