站長阿川

站長阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

站長精心設計,帶你實作 63 個小專案,得到作品集!

立即開始免費試讀!

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

image

剛寫 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 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
站長阿川

站長阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

站長精心設計,帶你實作 63 個小專案,得到作品集!

立即開始免費試讀!