声明文件(.d.ts)入门
本教程共 80 篇 · 第 61 篇 · 更新于 2026-08-10 · 约 12 分钟阅读
本节目标:搞清楚 .d.ts 文件是什么、为什么需要它、declare 关键字的用法,以及声明文件和普通 .ts 文件的区别。学完你就能看懂一个第三方声明文件在说什么。
一个没有类型的 JS 库
假设你从 npm 装了一个叫 math-utils 的包,它的 index.js 长这样:
// math-utils/index.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };
你在 TypeScript 项目里这样用:
import { add } from "math-utils";
add(1, 2); // ✅ 运行时没问题
add("hello", "world"); // ✅ 运行时也能跑,但结果不是你要的
问题来了——TypeScript 编译器不认识 math-utils。它不知道这个模块导出什么、add 的参数是什么类型、返回值是什么类型。编辑器不会给你补全,也不会报类型错误。
声明文件就是来解决这个问题的。它给 JavaScript 代码”贴上类型标签”,让 TypeScript 编译器知道一个 JS 变量、函数、类、模块的类型信息。
.d.ts 是什么
.d.ts 是 TypeScript 的类型声明文件(Declaration File)。它和 .ts 文件的核心区别:
.ts | .d.ts | |
|---|---|---|
| 内容 | 类型 + 运行时逻辑 | 仅类型声明,不含实现 |
| 编译产出 | 被编译为 .js | 不产生 .js 文件 |
| 运行时代码 | 有 | 完全没有 |
| 用途 | 写业务逻辑 | 描述已有 JS 代码的类型 |
一句话总结:.d.ts 文件只对编译器说话,不对运行时说话。你可以把它理解为”类型世界的说明书”。
declare 关键字
declare 是声明文件的基石。它的作用是告诉 TypeScript:“这个变量/函数/类已经存在于运行时,我只是在描述它的类型,不用检查它的实现。”
以下面的代码为例——在 .d.ts 文件里,所有声明都必须加 declare(或 export):
declare var / let / const
// 声明一个全局变量
declare var globalCounter: number;
// 只读的全局常量
declare const MAX_SIZE: number;
// 块级作用域的变量
declare let currentUser: string;
const 和 let 的区别跟 TypeScript 普通代码一样:const 声明的变量不能被重新赋值。
Tip如果一个变量确实不会被重新赋值,优先用
declare const。这给下游代码提供了更强的类型安全——谁尝试修改它,编译器就报错。
declare function
// 声明一个全局函数
declare function greet(name: string): string;
// 支持重载——多个 declare function 声明同一个名字
declare function getValue(key: string): string;
declare function getValue(key: number): number;
注意,declare function 只写函数签名,不写函数体。函数体在你的 JS 代码里,声明文件不需要知道它怎么实现的。
declare class
declare class Greeter {
constructor(message: string);
greeting: string;
showGreeting(): void;
}
这里声明了一个类,它有一个构造器、一个属性和一个方法。编译器会根据这个声明来检查你创建 Greeter 实例的代码是否正确——但 .d.ts 不会把 class 编译成任何 JS 代码。
declare enum
declare enum Direction {
Up,
Down,
Left,
Right
}
枚举有点特殊:它既是类型也是值。declare enum 告诉 TS 这个枚举的运行时值已经存在于 JS 侧。
declare namespace
declare namespace MyLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}
namespace 用来组织一组相关的声明。上面的代码等价于告诉 TS:“有一个叫 MyLib 的全局对象,它上面有 makeGreeting 方法和 numberOfGreetings 属性。”
你可以通过点号访问:
MyLib.makeGreeting("hello");
console.log(MyLib.numberOfGreetings);
NoteTS 7.0 仍然支持
namespace(内部模块)语法,但它主要用于声明文件中组织类型。在业务代码里,推荐用 ES 模块(import/export)代替namespace。
全局声明 vs 模块声明
声明文件分两种身份:
全局声明文件(脚本模式):文件顶层没有 import/export,所有 declare 的内容直接进入全局作用域。任何 .ts 文件都能直接用这些名字,不需要显式导入。
// global.d.ts —— 无 import/export,声明进入全局
declare function alert(message: string): void;
declare const VERSION: string;
模块声明文件(模块模式):文件顶层有 import 或 export,声明只在模块内可见。外部要通过 import 使用。
// math-utils.d.ts —— 有 export,声明是模块内的
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;
这块在后续章节会详细展开,现在只需要知道有这个区别。
编译器如何发现声明文件
你可能会想:我写了个 .d.ts 放项目里,TypeScript 怎么知道要读它?
TS 编译器按以下优先级查找声明文件:
tsconfig.json的include/files配置:如果你的声明文件在这些字段指定的路径下,TS 会直接加载。types字段:在 TS 7.0 中,types: []是默认值(6.x 及以前默认加载所有可见的@types/*)。你需要显式指定需要的类型包。typeRoots字段:自定义声明文件的查找目录,默认为node_modules/@types。- 三斜线指令:
/// <reference types="..." />或/// <reference path="..." />(旧写法,不推荐在新项目中使用)。
举个例子——如果你在项目根目录放了一个 custom.d.ts:
{
"compilerOptions": {
"strict": true,
"types": []
},
"include": ["src/**/*", "custom.d.ts"]
}
这样 custom.d.ts 里的全局声明就能在 src/ 下的所有文件中直接使用。
声明文件不包含什么
理解 “.d.ts 里不能写什么” 跟会写同等重要:
- 不能写函数体:声明文件只描述签名,不提供实现。
declare function fn(x: number): number { return x; }是错误的。 - 不能写可执行代码:
console.log、变量赋值、条件语句等都禁止。 - 不能直接引入 .js 运行时代码:声明文件是纯类型层面的事。
如果你在 .d.ts 里写了运行时逻辑,编译器直接报错。
一个完整的简单示例
假设有一个 math-utils 库,提供了以下全局 API:
// math-utils.js(运行时代码)
var MathUtils = {
version: "1.0.0",
add: function(a, b) { return a + b; },
multiply: function(a, b) { return a * b; },
Calculator: function(initial) {
this.value = initial || 0;
}
};
MathUtils.Calculator.prototype.add = function(n) {
this.value += n;
return this;
};
对应的声明文件:
// math-utils.d.ts(类型描述)
declare namespace MathUtils {
const version: string;
function add(a: number, b: number): number;
function multiply(a: number, b: number): number;
class Calculator {
constructor(initial?: number);
value: number;
add(n: number): this;
}
}
现在任何 TypeScript 文件里使用 MathUtils 都能获得完整的智能提示和类型检查。
接下来
入门内容就到这。你已经知道了 .d.ts 文件的基本语法:declare var/function/class/enum/namespace 和它们的用途。下一章我们开始写真正的全局库声明文件,从命名空间的结构到完整模板——把今天学的基本招式组装成一套完整的声明方案。