📝 引言
📌 场景/痛点
你在工作中一定遇到过这种情况:npm install 了一个好用的工具库,结果一引入,满屏飘红:Could not find a declaration file for module 'xxx'。
或者,项目里有一些遗留的 .js 文件,你不想重写它们,但希望在 TS 文件中引用它们时能有智能提示。
这时候,你需要手动编写 .d.ts 声明文件。 它是 TS 与 JS 世界之间的桥梁,也是前端工程师进阶的必修课。
✨ 最终效果
掌握本文后,你将能够为任何第三方库编写类型定义,甚至在完全没有源码的情况下,获得完美的代码补全。

📖 内容概览
本文将带你彻底搞懂声明文件:
🛠️ 正文
1. 环境准备
假设我们引入了一个名为 legacy-lib 的老旧 JS 库,它只导出了一个 calculate 函数和一个默认对象。
2. 核心概念:什么是声明文件?
.d.ts 文件(Definition File)是 TypeScript 的“契约”文件。
- 它只包含类型定义(interface, type, declare)。
- 不包含任何实现代码(不能写 function body)。
- 编译器在编译时读取它,但在最终输出 js 时会直接忽略它。
3. 核心关键字:declare
declare 告诉 TS 编译器:“这个变量/函数/类虽然在当前文件里没有实现,但它肯定存在于运行时,你放心大胆地用吧。”
3.1 常用声明语法
// 声明一个函数
declare function log(msg: string): void;
// 声明一个变量/常量
declare const version: string;
// 声明一个类 (只写属性和方法签名,不写大括号内的逻辑)
declare class Dog {
name: string;
constructor(name: string);
bark(): void;
}
// 声明一个模块 (最常用)
declare module "some-lib" {
export function doSomething(): boolean;
}
4. 实战:为第三方库编写类型
假设 legacy-lib 的源码是这样的 (JS):
// node_modules/legacy-lib/index.js
const calculate = (x, y) => x + y;
const defaultOptions = { debug: true };
module.exports = { calculate, defaultOptions };
现在,我们要为它写一个声明文件。
步骤一:创建类型文件
在项目的 src/types/ 下新建 legacy-lib.d.ts。
// src/types/legacy-lib.d.ts
// 1. 使用 declare module 包裹,指定要扩展的包名
declare module 'legacy-lib' {
// 2. 定义函数签名
export function calculate(x: number, y: number): number;
// 3. 定义对象导出
export const defaultOptions: {
debug: boolean;
};
}
步骤二:在代码中使用
无需任何配置,TS 会自动识别项目中所有的 .d.ts 文件。
// src/app.ts
import { calculate, defaultOptions } from 'legacy-lib';
// ✅ 有了类型!
const res = calculate(10, 20);
// ✅ 属性提示也出来了
if (defaultOptions.debug) {
console.log("Debug mode");
}
5. 进阶技巧:模块扩展与全局变量
5.1 扩展 window 对象
虽然之前讲过,这里再强调一下 .d.ts 写法。
// src/types/global.d.ts
// ⚠️ 注意:如果文件里没有任何 import/export,它会被视为全局脚本声明
// 如果文件里有 import/export,则必须使用 declare global
declare global {
interface Window {
myCustomApp: {
init(): void;
};
}
}
// 确保 export {} 让文件成为一个模块,避免污染全局作用域或冲突
export {};
6. 发布规范:package.json 中的 types
如果你是库的作者,想让用户下载你的包就能自动获得类型,请在 package.json 中配置:
{
"name": "awesome-lib",
"main": "./dist/index.js",
"types": "./dist/index.d.ts", // 🌟 关键字段!
// 或者用 TypeScript 的旧称 "typings"
"typings": "./dist/index.d.ts"
}
这样,当用户 import … from 'awesome-lib' 时,TS 会自动去寻找对应的 .d.ts 文件。
❓ 常见问题
Q1: index.d.ts、@types/name 和 node_modules 是什么关系?
A:
- 自动安装:当你安装 lodash 时,如果它自带的 package.json 里有 types 字段,TS 会用那个。
- @types:如果库本身没写类型,你可以安装 @types/lodash(这是社区贡献的)。
- 自己写:如果社区也没有(或者定义错了),你就在自己项目的 src/types 里写 .d.ts 覆盖它。
Q2: 我写了 .d.ts 文件,为什么还是提示找不到?
A: 检查 tsconfig.json:
- 确保 include 包含了你存放 .d.ts 的文件夹。
- 如果是全局声明,确保 typeRoots 配置正确(默认是 node_modules/@types 和同级目录下的 **/*.d.ts)。
Q3: declare module 'any-lib' 写完后,里面的类型能导入 TS 类吗?
A: 可以!
import { User } from './models'; // 导入项目内的类型
declare module 'some-api' {
// 在声明中使用外部类型
export function getUser(): User;
}
这样你的类型就能闭环了。
🎯 总结
声明文件是 TypeScript 生态繁荣的基石。本文我们掌握了:
🚀 下期预告:
随着项目越来越大,类型检查越来越慢,每次保存都要等半天 tsc 编译。
下一篇文章我们将深入 《类型性能优化:让你的大型项目飞起来》,掌握加速编译的核心技巧。
💬 互动环节:
你平时在项目中写过自定义的 .d.ts 吗?遇到过最难填的坑是什么?
如果觉得文章帮你解决了类型报错,请点赞👍、收藏⭐、关注👀,下期更精彩!





