
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`。

例如，为一个通过 `<script>` 标签引入的 `MY_GLOBAL_LIB` 库创建声明：
```typescript
// file: types/global.d.ts

interface LibConfig {
  apiKey: string;
}

declare function MY_GLOBAL_LIB(config: LibConfig): void;
```
只要这个 `.d.ts` 文件被包含在你的项目中，你就可以在任何地方安全地调用 `MY_GLOBAL_LIB(...)`，并获得类型检查。

### 总结

声明文件是 TypeScript 生态系统中不可或缺的粘合剂。它们是 TypeScript 能够理解和赋能庞大 JavaScript 世界的基石。

*   `.d.ts` 文件只包含**类型声明**，没有实现。
*   在动手编写前，**务必先在 `@types` 中搜索**。
*   对于模块化的 JS 库，核心是使用 `declare module '...'` 来包裹你的类型声明。
*   对于全局脚本，使用 `declare var/let/const/function`。

编写 `.d.ts` 文件可能起初看起来有些令人生畏，但它实际上是一个非常有价值的练习。它不仅能让你当前的项目变得更健壮，还能迫使你更深入地去理解你所依赖的 JavaScript 库的 API 设计。这是每一位专业 TypeScript 开发者都应具备的“超能力”。

