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 按以下顺序查找类型声明:

  1. 包内的 index.d.ts(库自带类型)
  2. node_modules/@types/ 下的声明
  3. tsconfig.jsontypeRootstypes 指定的位置

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) 基本规则

(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 包有社区审核。性能收益远大于风险。

📖 小节

📝 作业

  1. 基础题(难度⭐):为一个假设的 math-helpers JS 库编写声明文件,包含 add(a, b)subtract(a, b) 和常量 PI
  2. 进阶题(难度⭐⭐):写一个声明文件扩展 String 接口,添加 reverse(): string 方法。思考:为什么扩展内置类型需要放在 .d.ts 文件中?
  3. 挑战题(难度⭐⭐⭐):为一个没有类型的旧 JavaScript SDK 编写完整的声明文件——包含命名空间 SDK、类 SDK.Client(构造函数+方法)、枚举 SDK.EventType 和全局函数 SDK.init(options)
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏