Skip to content

在 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 设置(确保 allowJscheckJstrue),就可以开始了。

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 项目赋能的、触手可及的强大盟友。

不知道说啥了很无语了