刚写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一下就给你标红,说“这玩意没类型,我没法检查”。
这时候,你有两个选择:
- 改人家JS文件,变成
.ts
—— 别想,那是别的团队维护的。 - 写个
.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
文件的潜规则
-
文件名无所谓,但最好有意义
比如axios.d.ts
、env.d.ts
,一看就知道是干啥的。 -
内容只能是类型相关
你不能在.d.ts
里写const x = 1
,会报错。它只能有type
、interface
、declare
这些。 -
declare module 是“声明模块”的万能钥匙
第三方库没类型?用它!JS 文件想加类型?用它! -
别滥用,小心“类型幻觉”
你写了个declare const api: any;
,确实不报错了,但等于啥也没做。类型检查形同虚设,别骗自己。
结尾
.d.ts
文件用得好,它让你的项目更健壮;用得烂,它让你的类型系统变成“皇帝的新衣”。
(写完这篇,我回头看了看项目里的十几个 .d.ts
文件,叹了口气:是时候重构了……)