為什麼你的 TypeScript 專案裡,總會有幾個 .d.ts 文件?

剛寫 TS 的時候,我看到專案裡莫名其妙冒出個 types.d.ts 或者 global.d.ts,心裡是有點懵的。啥?這個文件是幹啥的?我 .ts 文件不是已經寫類型了嗎?為什麼還要多此一舉?

後來,專案越做越大,第三方庫用得越來越多,加上團隊裡有人用 JS、有人用 TS,我才發現——.d.ts 文件,其實是個“救火隊員”。

image

一、啥時候你會突然需要 .d.ts

先說個場景。

我們團隊之前接入了一個老系統,人家封裝了一個 JS 工具庫,叫 legacy-utils.js,裡面一堆函數:

// legacy-utils.js
function formatDate(date) {
  return date.toISOString().slice(0, 10);
}

function calculateTax(amount, rate) {
  return amount * rate * 0.01;
}

我們在 TS 專案裡直接 import:

import { formatDate } from './legacy-utils';

formatDate(new Date()); // 報錯:Could not find a declaration file...

紅了! VSCode 一下就給你標紅,說“這玩意沒類型,我沒法檢查”。

這時候,你有兩個選擇:

  1. 改人家 JS 文件,變成 .ts —— 別想,那是別的團隊維護的。
  2. 寫個 .d.ts 文件,告訴 TypeScript:“別慌,我知道它有啥類型。”

於是,我默默在專案裡建了個 types/legacy-utils.d.ts

// types/legacy-utils.d.ts
declare module 'legacy-utils' {
  export function formatDate(date: Date): string;
  export function calculateTax(amount: number, rate: number): number;
}

然後,在 tsconfig.json 裡確保 typeRoots 包含了 types 目錄。

再回到程式碼裡,紅波浪線沒了,自動補全也有了,世界清淨了。

那一刻我悟了:.d.ts 就是給 JS 打“類型補丁”的。

二、還有啥場景會用到它?

場景1:全局變數?別慌,.d.ts 來兜底

有些老專案,喜歡把變數掛到 window 上:

// index.html
<script>
  window.APP_CONFIG = { apiUrl: 'https://api.example.com' };
</script>

你在 TS 裡寫:

console.log(window.APP_CONFIG.apiUrl); // 類型“Window & typeof globalThis”上不存在屬性“APP_CONFIG”

煩不煩?煩。

解決方法:建個 global.d.ts

// global.d.ts
interface Window {
  APP_CONFIG: {
    apiUrl: string;
  };
}

保存,刷新,紅波浪線消失。舒服了。

我管這個叫“強行擴展”,雖然有點野路子,但專案要上線,誰還管你是不是優雅。

場景2:第三方庫沒提供類型?自己寫!

比如你用了某個小眾 npm 包,叫 super-fast-hash,作者沒寫類型,但你又不想用 any (畢竟開了 noImplicitAny)。

你可以:

// types/super-fast-hash.d.ts
declare module 'super-fast-hash' {
  const hash: (input: string) => string;
  export default hash;
}

然後你就可以:

import hash from 'super-fast-hash';
const result = hash('hello'); // 類型正確,不報錯

雖然這個庫可能就用一次,但至少程式碼看起來“專業”了點,對吧?

場景3:我想在多個文件裡用同一個 type,但不想到處 import

比如我們專案裡經常用到一種“用戶狀態”:

type UserStatus = 'active' | 'inactive' | 'pending';

如果每個文件都 import,太麻煩。不如:

// types/global-types.d.ts
type UserStatus = 'active' | 'inactive' | 'pending';

然後在 tsconfig.json 裡加:

{
  "compilerOptions": {
    "typeRoots": [
      "node_modules/@types",
      "types"
    ]
  }
}

這樣,所有 .ts 文件裡都能直接用 UserStatus,不用 import

是不是有點“全局污染”?是。但小專案圖個省事。

三、.d.ts 文件的潛規則

  1. 文件名無所謂,但最好有意義
    比如 axios.d.tsenv.d.ts,一看就知道是幹啥的。

  2. 內容只能是類型相關
    你不能在 .d.ts 裡寫 const x = 1,會報錯。它只能有 typeinterfacedeclare 這些。

  3. declare module 是“聲明模塊”的萬能鑰匙
    第三方庫沒類型?用它!JS 文件想加類型?用它!

  4. 別濫用,小心“類型幻覺”
    你寫了個 declare const api: any;,確實不報錯了,但等於啥也沒做。類型檢查形同虛設,別騙自己。

針尾

.d.ts 文件用得好,它讓你的專案更健壯;用得爛,它讓你的類型系統變成“皇帝的新衣”。

(寫完這篇,我回頭看了看專案裡的十幾個 .d.ts 文件,嘆了口氣:是時候重構了……)


原文出處:https://juejin.cn/post/7542769726176624640


精選技術文章翻譯,幫助開發者持續吸收新知。
按讚的人:

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝12   💬6   ❤️5
499
🥈
我愛JS
📝1   💬7   ❤️4
102
🥉
AppleLily
📝1   💬4   ❤️1
57
#4
💬1  
5
#5
xxuan
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次