TypeScript: TypeScript 声明文件
最后更新:2026-08-26
声明文件(.d.ts)是 TypeScript 与 JavaScript 世界的桥梁——它为没有类型信息的 JS 代码提供类型描述,让你在 TS 项目中安全使用任何 JS 库。
1. 什么是声明文件
(1) 问题:JS 库没有类型
TYPESCRIPT
// 使用 lodash —— 一个纯 JavaScript 库
import _ from "lodash";
// ❌ TypeScript 报错:找不到模块 "lodash" 的声明文件
let result = _.chunk([1, 2, 3, 4], 2);
(2) 解决:声明文件提供类型信息
声明文件以 .d.ts 为后缀,只包含类型声明,不包含实现代码:
TYPESCRIPT
// lodash.d.ts —— 声明文件(只描述类型,不提供实现)
declare module "lodash" {
export function chunk<T>(array: T[], size: number): T[][];
export function debounce(func: Function, wait: number): Function;
// ... 更多声明
}
有了声明文件,TypeScript 就能理解 lodash 的 API 并提供类型检查和代码提示。
(3) 声明文件的三种来源
| 来源 | 说明 | 示例 |
|---|---|---|
| 内置声明 | TypeScript 自带(DOM、ES2020等) | lib.dom.d.ts |
| 包内自带 | 库作者在包内包含 .d.ts |
axios/index.d.ts |
| DefinitelyTyped | 社区维护的第三方声明 | @types/lodash |
2. @types 包的安装与使用
(1) 查找类型声明
BASH
# 查看某个包是否有 @types 声明
npm info @types/lodash
# 安装类型声明
npm install @types/lodash --save-dev
(2) 安装后的效果
TYPESCRIPT
// 安装 @types/lodash 后——完整的类型支持
import _ from "lodash";
let chunks: number[][] = _.chunk([1, 2, 3, 4], 2); // ✅ 类型安全
let debounced = _.debounce(() => {}, 300); // ✅ 自动补全
(3) 常用 @types 包
| 包名 | 对应库 |
|---|---|
@types/node |
Node.js |
@types/lodash |
Lodash |
@types/express |
Express |
@types/jest |
Jest |
@types/react |
React |
@types/jquery |
jQuery |
(4) 类型声明的自动发现
TypeScript 按以下顺序查找类型声明:
- 包内的
index.d.ts(库自带类型) node_modules/@types/下的声明tsconfig.json中typeRoots和types指定的位置
3. 编写自己的声明文件
(1) 全局变量声明
当通过 script 标签引入 JS 库时,需要声明全局变量:
TYPESCRIPT
// globals.d.ts
declare var jQuery: (selector: string) => HTMLElement;
declare var $: typeof jQuery;
// 使用
let el = $(".container"); // ✅ 类型安全
(2) 全局函数声明
TYPESCRIPT
// globals.d.ts
declare function ga(command: string, ...args: any[]): void;
declare function gtag(type: string, eventName: string, params?: Record<string, any>): void;
// 使用
ga("send", "pageview"); // ✅
gtag("event", "click", { value: 1 }); // ✅
(3) 模块声明
当使用没有类型的 npm 包时,声明模块:
TYPESCRIPT
// declarations.d.ts
declare module "untyped-lib" {
export function doSomething(value: string): number;
export const version: string;
export default class Client {
constructor(options: { host: string; port: number });
connect(): Promise<void>;
}
}
// 使用
import Client, { doSomething, version } from "untyped-lib";
(4) 模块扩展——给已有模块添加类型
TYPESCRIPT
// 扩展 express 模块
declare module "express" {
interface Request {
user?: {
id: number;
name: string;
};
}
}
// 现在可以在 express.Request 上安全使用 user 属性
import { Request } from "express";
function handler(req: Request) {
if (req.user) {
console.log(req.user.name); // ✅ 类型安全
}
}
▶ 示例:为自定义JS工具编写声明
TYPESCRIPT
// 假设有一个 legacy-utils.js 文件没有类型
// legacy-utils.d.ts —— 为它编写声明
declare module "legacy-utils" {
/**
* 格式化日期为指定模式
* @param date - 日期对象或时间戳
* @param pattern - 格式模式,如 "YYYY-MM-DD"
*/
export function formatDate(date: Date | number, pattern: string): string;
/**
* 深拷贝对象
*/
export function deepClone<T>(obj: T): T;
/**
* 防抖函数
*/
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void;
/**
* 默认导出:工具集对象
*/
const utils: {
formatDate: typeof formatDate;
deepClone: typeof deepClone;
debounce: typeof debounce;
};
export default utils;
}
// 使用——完整类型支持
import utils from "legacy-utils";
let dateStr = utils.formatDate(new Date(), "YYYY-MM-DD");
let cloned = utils.deepClone({ name: "Charlie" });
let debounced = utils.debounce((x: number) => console.log(x), 300);
4. 声明文件的编写规则
(1) 基本规则
.d.ts文件只包含声明,不含实现- 使用
declare关键字声明外部实体 - 不需要
export——除非在模块声明中 - 顶层
export使文件成为模块声明(而非全局声明)
(2) 三种声明作用域
TYPESCRIPT
// ── 全局声明(没有 import/export) ──
// 文件中所有声明自动对整个项目可见
declare var GLOBAL_CONFIG: { api: string };
declare function globalHelper(): void;
// ── 模块声明 ──
declare module "my-lib" {
export function helper(): void;
}
// ── 文件模块声明 ──
// 文件顶部有 import/export → 整个文件是模块
import { User } from "./types";
export declare function processUser(user: User): void;
(3) 类型导出的规范
TYPESCRIPT
// ✅ 推荐——导出接口和类型
export interface User {
id: number;
name: string;
}
export type UserId = number;
// ✅ 推荐——导出函数签名
export declare function getUser(id: number): User;
// ❌ 不推荐——导出具体实现(.d.ts 不应有实现)
// export function getUser(id: number): User { return ...; }
5. tsconfig 中的类型配置
(1) typeRoots——指定类型声明目录
JSON
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./src/types"
]
}
}
(2) types——指定要包含的类型包
JSON
{
"compilerOptions": {
"types": ["node", "jest", "lodash"]
// 只包含这三个 @types 包,忽略其他
}
}
(3) 三种严格性选项
JSON
{
"compilerOptions": {
"noImplicitAny": true, // 禁止隐式 any
"strict": true, // 开启所有严格检查
"skipLibCheck": true // 跳过 .d.ts 文件的类型检查(加快编译)
}
}
❓ 常见问题
Q 什么时候需要写声明文件?
A 三种场景:(1) 使用的 JS 库没有 @types 包 (2) 通过 script 标签引入的全局变量 (3) 需要给已有模块添加自定义属性。大部分情况安装 @types 就够了,自己写声明文件是比较少见的操作。
Q declare module 和 declare global 有什么区别?
A
declare module "xxx" 声明一个外部模块的类型——用于给 npm 包提供类型。declare global 在模块文件中向全局命名空间添加声明——用于扩展全局类型(如 Window)。两者作用域不同:module 是模块级,global 是全局级。Q @types 包和库自带类型哪个优先?
A 库自带类型优先。现代库(如 axios、zod)已经在包内包含
index.d.ts,不需要额外安装 @types。只有库不自带类型时才需要 @types。如果两者都存在,TypeScript 优先使用包内的声明。Q skipLibCheck 该不该开?
A 建议开启。skipLibCheck 跳过所有
.d.ts 文件的类型检查,能显著加快编译速度(特别是大型项目)。缺点是可能错过第三方类型声明中的错误——但这个风险很小,因为 @types 包有社区审核。性能收益远大于风险。📖 小节
- 声明文件
.d.ts为 JavaScript 代码提供类型信息——只含声明不含实现 - 类型声明三种来源:TypeScript 内置、库自带、@types 社区包
- 用
npm install @types/包名安装第三方类型声明 - 自己写声明文件的场景:无类型的 JS 库、全局变量、模块扩展
declare关键字声明外部实体,declare module声明模块类型- tsconfig 的 typeRoots/types/noImplicitAny/skipLibCheck 控制类型查找和严格度
📝 作业
- 基础题(难度⭐):为一个假设的
math-helpersJS 库编写声明文件,包含add(a, b)、subtract(a, b)和常量PI。 - 进阶题(难度⭐⭐):写一个声明文件扩展
String接口,添加reverse(): string方法。思考:为什么扩展内置类型需要放在.d.ts文件中? - 挑战题(难度⭐⭐⭐):为一个没有类型的旧 JavaScript SDK 编写完整的声明文件——包含命名空间
SDK、类SDK.Client(构造函数+方法)、枚举SDK.EventType和全局函数SDK.init(options)。