为什么你的TypeScript项目里,总会有几个.d.ts文件?

0 阅读3分钟

刚写TS那会儿,我看到项目里莫名其妙冒出个types.d.ts或者global.d.ts,心里是有点懵的。啥?这文件是干啥的?我 .ts文件不是已经写类型了吗?为啥还要多此一举?

后来,项目越做越大,第三方库用得越来越多,加上团队里有人用JS、有人用TS,我才发现——.d.ts 文件,其实是个“救火队员”

一、啥时候你会突然需要 .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 文件,叹了口气:是时候重构了……)