讲解

把一个 JavaScript 项目迁移到 TypeScript,官方推荐的路线是渐进的四步。第一步:接入编译——把 tsc 加进构建,allowJs: true 让 .js 文件也被纳管,checkJs: false 先不检查它们,此时 TypeScript 只负责「看着」。第二步:逐个文件改后缀 .js → .ts,修显式报错;文件多的话按目录推进,每批可独立合并。第三步:提高严格度——从 strict: false 起步,按 strictNullChecks 最后开的顺序逐项打开(空值错误最多,留到基础设施就绪后)。第四步:消灭逃生舱——扫描 any、as unknown、@ts-ignore,制定新增代码零容忍规则。

两个加速工具:一是 JSDoc + checkJs——不改后缀,在 .js 文件里写 JSDoc 类型注释并开 checkJs,就能先享受检查(老项目、脚本目录很合适);二是 @ts-expect-error 注释——压制下一行错误,但如果错误消失它会自己报错(比 @ts-ignore 好,不会烂在代码里)。

最后是全教程的浓缩清单。要:开启 strict;能推断就不标注,参数与返回值除外;对象契约用 interface,其余用 type;外部数据入口做运行时校验(守卫或 zod);import type 表达纯类型依赖。不要:不用 any 消红;不写 as unknown as;不让类型体操进入业务代码;不在构造函数里依赖多态;不为「以后可能用到」提前抽象泛型。类型系统是工具,目标是让代码更敢说真话——而不是更复杂。

示例

JSDoc + checkJs:不改后缀先获得检查(js 块会以 --allowJs --checkJs 编译验证):

// @ts-check
/**
 * @param {number} price
 * @param {number} count
 * @returns {number}
 */
function total(price, count) {
  return price * count;
}

console.log(total(39, 2));
// total("39", 2); // 若取消注释:checkJs 会报 string 不能赋给 number

@ts-expect-error:压制已知错误,错误消失时自动报警:

// @ts-expect-error — 演示:故意把 number 赋给 string,这行错误被压制
const wrong: string = 123;

const right: string = "123";
console.log(typeof wrong, right);

一份收尾用的「迁移期」tsconfig(先松后紧的中间态):

{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src"]
}

常见坑

  • 一次性全量改写:迁移期新旧混跑是常态,按目录小步合并,每步保持 CI 绿。
  • 把 @ts-ignore 当橡皮泥:它不验证「错误还在不在」,优先 @ts-expect-error;两者都要带原因注释。
  • 迁移完成度以报错为零衡量:真正的终点是新增代码不引入 any/as——配 lint 规则(no-explicit-any)守住增量。
  • JSDoc 与 .ts 混用漂移:同一对象两处描述迟早不一致,JSDoc 是过渡手段,最终归一到 .ts。

小结

迁移四步:纳管 → 改后缀 → 提严格度 → 消灭逃生舱;checkJs 与 @ts-expect-error 是两个加速器;照清单守住日常。教程到此结束——接下来去真实项目里把类型写成团队的生产力。