24.声明文件.d.ts 副本.md 来源:https://my.feishu.cn/file/ZSHUbVZs7o5rSPxAFFscN96WnJg 采集状态:已完成正文采集;已从查看代码模式逐行提取,行号 1 至 152 连续,并核对滚动到底。 TypeScript 的强大之处在于其静态类型系统,它能在我们编码时就提供无与伦比的智能提示、错误检查和代码导航。但这引出了一个关键问题:当我们的 TypeScript 项目需要使用一个纯 JavaScript 编写的库(如一个 jQuery 插件、一个旧版的 npm 包,或者一个内部的工具库)时,会发生什么? 对于 TypeScript 编译器来说,这个 JavaScript 库就像一个“黑箱”。它不知道这个库导出了哪些函数,这些函数需要什么参数,又会返回什么。因此,编译器只能无奈地将所有从该库导入的东西都视为 `any` 类型。这意味着,我们失去了所有类型安全保障,回到了“刀耕火种”的 JavaScript 时代。 为了解决这个问题,TypeScript 提供了一套解决方案,其核心就是**声明文件 (Declaration Files)**,它们以 `.d.ts` 为后缀。 ### 什么是声明文件? 你可以将 `.d.ts` 文件想象成一本**API 使用手册**。它只**描述** JavaScript 代码的“形状”(shape),而**不包含任何具体的实现**。它告诉 TypeScript 编译器: * 这个库里有哪些可用的变量、函数和类。 * 这些函数的参数是什么类型,返回值是什么类型。 * 这些类有哪些属性和方法。 * ...以及所有关于其公共 API 的类型信息。 它是一座桥梁,连接了 TypeScript 的静态类型世界和 JavaScript 的动态运行时世界。 ### 在你动手写之前:先寻找现有的声明文件 在你准备为某个库从零开始编写 `.d.ts` 文件之前,请务必先做两件事,这能为你节省大量时间: 1. **检查库本身**:许多现代 JavaScript 库在发布时已经**内置了**自己的 `.d.ts` 文件。这是一个越来越普遍的最佳实践。 2. **搜索 DefinitelyTyped**:DefinitelyTyped 是一个庞大的、由社区驱动的开源项目,它为数千个流行的 JavaScript 库提供了高质量的 `.d.ts` 文件。这些文件都发布在 npm 的 `@types` scope 下。 例如,如果你想在项目中使用 `lodash`,你只需要运行: ```bash npm install --save-dev @types/lodash ``` 安装后,TypeScript 编译器会自动找到并使用这些类型定义,你就可以享受到对 `lodash` 的完整类型支持了。 **只有在这两种方式都失败时,我们才需要亲手创建自己的声明文件。** ### 动手实践:为一个 JS 模块创建声明文件 假设我们有一个简单的、内部使用的 JavaScript 工具库 `string-utils.js`: ```javascript // file: lib/string-utils.js function padLeft(str, len, char) { return char.repeat(len - str.length) + str; } function countChars(str) { return str.length; } module.exports = { padLeft, countChars, }; ``` 这是一个典型的 CommonJS 模块。现在,让我们在 TypeScript 项目中使用它: ```typescript // file: src/index.ts import * as utils from '../lib/string-utils'; // TypeScript 不知道 utils 的类型,会将其视为 any const result = utils.padLeft("hello", 10, " "); // 没有类型提示,容易出错 ``` 为了修复这个问题,我们需要创建一个 `string-utils.d.ts` 文件。 #### 第 1 步:创建 `.d.ts` 文件 在你的项目中创建一个文件,例如 `types/string-utils.d.ts`。 #### 第 2 步:使用 `declare module` 因为这是一个模块,我们需要使用 `declare module '...'` 语法。模块的名称必须与你在 `import` 语句中使用的路径完全匹配。 ```typescript // file: types/string-utils.d.ts declare module '../lib/string-utils' { // 我们将在这里描述模块的 API } ``` #### 第 3 步:描述导出的成员 现在,在 `declare module` 块内部,我们像编写普通 TypeScript 代码一样,使用 `export` 来描述 `string-utils.js` 导出的内容。 ```typescript // file: types/string-utils.d.ts declare module '../lib/string-utils' { /** * Pads a string on the left. * @param str The string to pad. * @param len The total desired length. * @param char The character to pad with. */ export function padLeft(str: string, len: number, char: string): string; /** * Counts the characters in a string. * @param str The input string. */ export function countChars(str: string): number; } ``` **关键点**: * 我们只写了函数的签名,没有函数体 `{}`。 * 我们使用了 `export` 关键字,因为原始 JS 模块导出了这些函数。 * 添加 JSDoc 注释是一个非常好的习惯,它能让使用者的体验更上一层楼。 #### 第 4 步:让 TypeScript 找到它 最后,确保你的 `tsconfig.json` 能够找到你创建的声明文件。通常,`include` 配置会自动处理,或者你可以通过 `typeRoots` 明确指定。 现在,回到我们的 `index.ts` 文件,你会发现奇迹发生了: ```typescript // file: src/index.ts import * as utils from '../lib/string-utils'; // 1. 获得完整的智能提示 // 2. 获得参数类型检查 const result = utils.padLeft("hello", 10, " "); // utils.padLeft("hello", 10, 123); // 编译时错误: Argument of type 'number' is not assignable to parameter of type 'string'. ``` ### 声明全局变量 对于那些不通过模块系统、而是直接向全局作用域(如浏览器的 `window` 对象)添加变量的旧脚本,我们可以使用 `declare var`, `declare let`, 或 `declare const`。 例如,为一个通过 `