声明文件与类型扩展
核心问题解析
问题一:第三方 JS 库怎么加类型?
你引入了一个纯 JavaScript 库:
// utils.js
export function formatDate(date) {
return date.toLocaleDateString();
}在 TypeScript 中导入:
import { formatDate } from './utils';
// ❌ 报错:找不到模块声明解决方案:编写 .d.ts 声明文件,为它提供类型。
// utils.d.ts
declare module './utils' {
export function formatDate(date: Date): string;
}现在 TS 能正确识别 formatDate 的类型了。
这就是为什么有 @types/lodash、@types/react 等包。
问题二:全局变量如何声明?
有些库会挂载到全局对象上,比如:
<script src="https://cdn.com/analytics.js"></script>然后在 JS 中直接使用:
Analytics.track('page_view');但在 TS 中会报错:Cannot find name 'Analytics'.
答案:使用 declare global 声明全局类型。
问题三:怎么给模块扩展类型?
你想给一个已有模块(如 axios)添加自定义方法或属性:
// 想要:
axios.myCustomMethod();但 axios 的类型定义中没有这个方法。
答案:通过模块补充(Module Augmentation) 扩展其类型。
学习目标详解
目标一:编写 .d.ts 声明文件
.d.ts 文件只包含类型声明,不生成任何 JS 代码。
1. 创建声明文件
推荐命名:types.d.ts 或 global.d.ts,放在 src 目录下,TS 会自动识别。
2. 基本语法
// 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 文件声明类型
// utils.d.ts
declare module './utils' {
export function formatDate(date: Date): string;
export const PI: number;
}确保 allowJs: true 在 tsconfig.json 中。
目标二:使用 declare module 和 declare global
1. declare module 'module-name'
用于为模块添加类型。
// types.d.ts
declare module 'my-library' {
export function doSomething(): void;
export const version: string;
}
// 使用
import { doSomething } from 'my-library';2. declare global
用于扩展全局作用域。
// 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 不生效现在可以在任何地方使用:
window.Analytics.track('login');
debug('Debug mode');目标三:扩展已有类型(模块补充)
你想给 express 的 Request 对象添加 user 属性:
app.get('/profile', (req, res) => {
req.user.id; // ❌ 默认不存在
});✅ 使用模块补充(Module Augmentation):
// 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.md2. 编写 index.d.ts
// index.d.ts
export function utilityFn(input: string): number;
export const MAGIC_NUMBER: 100;3. 配置 package.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
npm publish之后用户可通过:
npm install --save-dev @types/my-lib自动获得类型支持。
总结:本节核心要点
| 场景 | 方法 | 示例 |
|---|---|---|
| 本地 JS 库 | declare module './xxx' | 为 utils.js 写 utils.d.ts |
| 全局变量 | declare global | 扩展 Window 接口 |
| 模块扩展 | declare module 'mod' | 给 express.Request 加字段 |
| 发布类型包 | @types/* + types 字段 | 创建 @types/my-lib |
声明文件是 TypeScript 生态的“胶水”,让你能在类型安全的前提下使用任意 JS 代码。
练习题
练习题 1:为本地 JS 文件写声明
为以下 helpers.js 文件编写 helpers.d.ts:
// helpers.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export const API_URL = 'https://api.example.com';要求:
capitalize参数为string,返回stringAPI_URL为string
练习题 2:声明全局变量
假设页面引入了 chart.js,全局有 Chart 构造函数:
new Chart(ctx, { type: 'bar', data: ... });编写 global.d.ts 声明 Chart 类型:
// global.d.ts
declare class Chart {
constructor(context: CanvasRenderingContext2D, config: any);
}
declare global {
interface Window {
Chart: typeof Chart;
}
}
export {};练习题 3:模块补充实战
扩展 NodeJS.ProcessEnv 接口,添加自定义环境变量:
// 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 的快捷方法:
axios.getWithCancel('/data', () => { /* cancel logic */ });实现类型扩展:
// 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 库:
// simple-event.js
const emitter = {
on(event, fn) { /*...*/ },
emit(event, data) { /*...*/ }
};
export default emitter;编写 index.d.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 开发者的必备技能。