Skip to content

声明文件与类型扩展

核心问题解析

问题一:第三方 JS 库怎么加类型?

你引入了一个纯 JavaScript 库:

js
// utils.js
export function formatDate(date) {
  return date.toLocaleDateString();
}

在 TypeScript 中导入:

ts
import { formatDate } from './utils';
// ❌ 报错:找不到模块声明

解决方案:编写 .d.ts 声明文件,为它提供类型。

ts
// utils.d.ts
declare module './utils' {
  export function formatDate(date: Date): string;
}

现在 TS 能正确识别 formatDate 的类型了。

这就是为什么有 @types/lodash@types/react 等包。

问题二:全局变量如何声明?

有些库会挂载到全局对象上,比如:

html
<script src="https://cdn.com/analytics.js"></script>

然后在 JS 中直接使用:

js
Analytics.track('page_view');

但在 TS 中会报错:Cannot find name 'Analytics'.

答案:使用 declare global 声明全局类型。

问题三:怎么给模块扩展类型?

你想给一个已有模块(如 axios)添加自定义方法或属性:

ts
// 想要:
axios.myCustomMethod();

axios 的类型定义中没有这个方法。

答案:通过模块补充(Module Augmentation) 扩展其类型。

学习目标详解

目标一:编写 .d.ts 声明文件

.d.ts 文件只包含类型声明,不生成任何 JS 代码。

1. 创建声明文件

推荐命名:types.d.tsglobal.d.ts,放在 src 目录下,TS 会自动识别。

2. 基本语法

ts
// types.d.ts
declare const VERSION: string;
declare function log(message: string): void;
declare class Logger {
  constructor(name: string);
  info(msg: string): void;
}

// 使用
log('App started'); // ✅ 类型安全

3. 为本地 JS 文件声明类型

ts
// utils.d.ts
declare module './utils' {
  export function formatDate(date: Date): string;
  export const PI: number;
}

确保 allowJs: truetsconfig.json 中。

目标二:使用 declare moduledeclare global

1. declare module 'module-name'

用于为模块添加类型。

ts
// types.d.ts
declare module 'my-library' {
  export function doSomething(): void;
  export const version: string;
}

// 使用
import { doSomething } from 'my-library';

2. declare global

用于扩展全局作用域

ts
// global.d.ts
declare global {
  interface Window {
    Analytics: {
      track(event: string): void;
      userId: string | null;
    };
  }

  const VERSION: string;
  function debug(...args: any[]): void;
}

export {}; // 必须导出,否则 declare global 不生效

现在可以在任何地方使用:

ts
window.Analytics.track('login');
debug('Debug mode');

目标三:扩展已有类型(模块补充)

你想给 expressRequest 对象添加 user 属性:

ts
app.get('/profile', (req, res) => {
  req.user.id; // ❌ 默认不存在
});

使用模块补充(Module Augmentation)

ts
// types/express.d.ts
import 'express';

declare module 'express' {
  export interface Request {
    user?: {
      id: number;
      name: string;
    };
  }
}

export {};

注意:必须先 import 'express'declare module,以触发补充机制。

目标四:发布 @types 兼容的类型包

如果你想为一个流行 JS 库创建公开的类型定义(类似 @types/lodash),步骤如下:

1. 创建项目结构

my-lib-types/
├── index.d.ts
├── package.json
└── README.md

2. 编写 index.d.ts

ts
// index.d.ts
export function utilityFn(input: string): number;
export const MAGIC_NUMBER: 100;

3. 配置 package.json

json
{
  "name": "@types/my-lib",
  "version": "1.0.0",
  "description": "TypeScript definitions for my-lib",
  "main": "",
  "types": "index.d.ts",
  "peerDependencies": {
    "typescript": ">=4.0"
  },
  "keywords": ["ambient", "declarations", "types"],
  "license": "MIT"
}

4. 发布到 npm

bash
npm publish

之后用户可通过:

bash
npm install --save-dev @types/my-lib

自动获得类型支持。

总结:本节核心要点

场景方法示例
本地 JS 库declare module './xxx'utils.jsutils.d.ts
全局变量declare global扩展 Window 接口
模块扩展declare module 'mod'express.Request 加字段
发布类型包@types/* + types 字段创建 @types/my-lib

声明文件是 TypeScript 生态的“胶水”,让你能在类型安全的前提下使用任意 JS 代码。

练习题

练习题 1:为本地 JS 文件写声明

为以下 helpers.js 文件编写 helpers.d.ts

js
// helpers.js
export function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
export const API_URL = 'https://api.example.com';

要求:

  • capitalize 参数为 string,返回 string
  • API_URLstring

练习题 2:声明全局变量

假设页面引入了 chart.js,全局有 Chart 构造函数:

js
new Chart(ctx, { type: 'bar', data: ... });

编写 global.d.ts 声明 Chart 类型:

ts
// global.d.ts
declare class Chart {
  constructor(context: CanvasRenderingContext2D, config: any);
}

declare global {
  interface Window {
    Chart: typeof Chart;
  }
}

export {};

练习题 3:模块补充实战

扩展 NodeJS.ProcessEnv 接口,添加自定义环境变量:

ts
// types/env.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' \| 'production' \| 'test';
      API_KEY: string;
      PORT?: string;
    }
  }
}

export {};

// 使用
process.env.API_KEY; // ✅ 类型安全

练习题 4:补充第三方模块

你想给 axios 添加一个 cancelToken 的快捷方法:

ts
axios.getWithCancel('/data', () => { /* cancel logic */ });

实现类型扩展:

ts
// types/axios.d.ts
import 'axios';
import axios from 'axios';

declare module 'axios' {
  export interface AxiosStatic {
    getWithCancel<T>(url: string, onCancel: () => void): Promise<T>;
  }
}

export {};

练习题 5:创建简单的 @types 包

创建一个 @types/simple-event 类型包,用于以下 JS 库:

js
// simple-event.js
const emitter = {
  on(event, fn) { /*...*/ },
  emit(event, data) { /*...*/ }
};
export default emitter;

编写 index.d.ts

ts
// index.d.ts
interface EventMap {
  [event: string]: (...args: any[]) => void;
}

declare const emitter: {
  on<E extends keyof EventMap>(event: E, handler: EventMap[E]): void;
  emit<E extends keyof EventMap>(event: E, ...args: Parameters<EventMap[E]>): void;
};

export default emitter;

掌握声明文件和类型扩展,你就拥有了“让任何 JS 代码变安全”的能力。无论是维护老项目还是集成第三方库,这都是 TypeScript 开发者的必备技能。