

在 TypeScript 的世界里，一个常见的误解是：拥抱 TypeScript 意味着必须将整个 JavaScript 项目进行一次痛苦的、颠覆性的重写。但事实远非如此。TypeScript 的设计哲学中最精妙的一点，正是它作为 JavaScript 的“超集”所带来的**渐进式采纳**能力。

TypeScript 并非 JavaScript 的替代品，而是一个强大的协作者。它可以在不扰乱现有工作流的情况下，悄无声息地进入你的 JavaScript 项目，逐步为其注入类型安全和开发时工具的强大能力。本文将探索两种将 TypeScript 集成到 JavaScript 项目中的核心策略：直接集成与 JSDoc 增强。

### 策略一：直接集成，让 TypeScript 文件与 JavaScript 文件共舞

这是最直接的策略，适用于你准备开始将部分 `.js` 文件迁移到 `.ts` 的场景。

#### 第 1 步：搭建桥梁 `tsconfig.json`

首先，我们需要为项目引入 TypeScript 并创建一个配置文件。

1.  **安装 TypeScript**：
    ```bash
    npm install typescript --save-dev
    ```

2.  **生成 `tsconfig.json`**：
    ```bash
    npx tsc --init
    ```

3.  **配置关键选项**：在生成的 `tsconfig.json` 文件中，找到并确保以下两个选项被设置为 `true`：

    ```json
    {
      "compilerOptions": {
        // ...
        "allowJs": true,     // 核心：允许编译器编译 .js 文件。
        "checkJs": true      // 推荐：让编译器开始报告 .js 文件中的类型错误。
        // ...
      }
    }
    ```
    *   `"allowJs": true` 是魔法的开关。它告诉 TypeScript 编译器：“请将 `.js` 文件也视为你项目的一部分。”
    *   `"checkJs": true` 则更进一步，它指示 TypeScript 对这些 `.js` 文件进行类型检查（基于其强大的类型推断能力和 JSDoc 注释）。

#### 第 2 步：从一个文件开始

迁移不必一步到位。选择一个风险较低、逻辑相对独立的文件开始，比如一个工具函数库 `utils.js`。

1.  **重命名**：将 `utils.js` 重命名为 `utils.ts`。
2.  **添加类型**：TypeScript 的类型推断已经能帮你发现很多问题了。现在，你可以开始为函数参数和返回值添加明确的类型注解。

**之前 (`utils.js`)：**
```javascript
function truncate(str, length) {
  return str.substring(0, length);
}
```

**之后 (`utils.ts`)：**
```typescript
function truncate(str: string, length: number): string {
  return str.substring(0, length);
}
```
就这样，`truncate` 函数现在是完全类型安全的了。任何试图传入非字符串或非数字参数的调用，都会在你的编辑器和编译时被立刻捕获。你的项目现在是一个 `.ts` 和 `.js` 文件和谐共存的状态。

### 策略二：JSDoc 增强，在 `.js` 文件中获得超能力

有时候，你可能因为团队规范、遗留系统或其他原因，无法或不想将文件重命名为 `.ts`。但这并不意味着你与类型安全无缘。TypeScript 能够理解并利用 **JSDoc 注释**来进行类型检查。

你只需完成上面提到的 `tsconfig.json` 设置（确保 `allowJs` 和 `checkJs` 为 `true`），就可以开始了。

#### 1. 为函数添加类型

使用 `@param` 和 `@returns` 标签，并用花括号 `{}` 标注类型。

```javascript
// file: src/formatters.js

/**
 * Greets a user by name.
 * @param {string} name - The name of the user.
 * @returns {string} The complete greeting.
 */
function greet(name) {
  return `Hello, ${name}!`;
}

greet(123); // 编辑器和 tsc 会报错：Argument of type 'number' is not assignable to parameter of type 'string'.
```
即使这仍然是一个 `.js` 文件，TypeScript 编译器和你的 IDE（如 VS Code）也能理解这个类型契约，并提供实时错误检查和智能提示。

#### 2. 定义复杂类型

JSDoc 的能力远不止于此。你可以使用 `@typedef` 来定义复杂的对象类型，甚至可以从其他文件导入类型定义。

**首先，创建一个类型定义文件（可以是 `.ts` 或 `.d.ts`）：**
```typescript
// file: src/types.ts
export interface User {
  id: number;
  username: string;
}
```

**然后，在你的 `.js` 文件中使用 `import()` 语法来引用它：**
```javascript
// file: src/api.js

/**
 * Fetches a user profile.
 * @param {number} userId
 * @returns {Promise<import('./types').User>} A promise that resolves to the user object.
 */
async function fetchUser(userId) {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

// TypeScript 会知道 fetchUser 返回的是 Promise<User>
fetchUser(1).then(user => {
  console.log(user.username); // 能够获得 'username' 的智能提示
  // console.log(user.email); // 会报错，因为 User 类型上没有 email 属性
});
```
这种 `import('./types').User` 语法是 JSDoc 的一个强大特性，它允许你在不改变 JavaScript 运行时行为的情况下，“借用” TypeScript 的类型定义。

### 总结：选择你的路径，或双管齐下

TypeScript 与 JavaScript 的协同工作并非一道单选题。它提供了一个灵活的、渐进的路径，让每个团队都能找到适合自己的节奏。

*   **路径 A：逐步迁移**。通过设置 `"allowJs": true`，你可以将 `.js` 文件逐个重命名为 `.ts`，在新文件中享受完整的 TypeScript 语法和功能。
*   **路径 B：JSDoc 增强**。通过设置 `"checkJs": true` 和编写 JSDoc 注释，你可以在保持 `.js` 文件不变的情况下，获得 TypeScript 带来的绝大部分类型检查和工具支持。

最棒的是，**这两个路径可以并行不悖**。在一个大型项目中，你可以：
1.  对所有**新功能**，使用 `.ts` 文件编写。
2.  对**现有关键的 `.js` 模块**，添加 JSDoc 注释以快速获得类型安全。
3.  在重构或修复旧模块时，顺便将其从 `.js` + JSDoc **迁移到 `.ts`**。

这种务实的策略，让 TypeScript 不再是一个遥远的目标，而是一个从今天起就能为你的 JavaScript 项目赋能的、触手可及的强大盟友。

