升级 TS 6 之前,这篇文章值得先看一遍。
前言:为什么这次升级特别容易翻车
TypeScript 6.0 在 2026 年 3 月正式发布。如果你去看官方发布说明,会发现一个很有意思的定位——这是最后一个基于 JavaScript 代码库的 TypeScript 版本。
TypeScript 7 将基于 Go 重写的原生编译器(Project Corsa),届时构建速度会有数量级提升。而 TS 6 的核心任务,就是清理旧时代配置包袱、对齐 TS 7 行为,让这个过渡尽可能平滑。
这意味着什么?意味着 TS 6 的变更不炫技,但很实在。它没有给你带来多少"哇塞"的新语法,而是把一批用了多年的默认值改了。
而这,恰恰是最容易让人翻车的地方。
你的项目里大概有这些情况吗?
- tsconfig.json 里没有显式写
strict - 用的是 CommonJS 风格的项目
- 依赖
@types/node等全局类型 - 用
baseUrl+paths做路径别名 - 项目结构里 tsconfig.json 和源码目录不在同一层
如果有,那升级 TS 6 后大概率会遇到编译报错。这篇文章帮你把坑提前排掉。
一、strict 默认变成 true
变更内容
表格
| 版本 | strict 默认值 |
|---|---|
| TS 5.x | false |
| TS 6.0 | true |
strict 不是单个开关,而是一组严格类型检查的集合:
{
"strict": true
}
等价于同时开启:
strictNullChecksnoImplicitAnystrictFunctionTypesstrictPropertyInitializationnoImplicitThisalwaysStrict
为什么会报错
很多老项目习惯不写 strict,依赖一些"宽松"的隐式类型推断。升级后这些代码会直接报错:
// ❌ 报错:Parameter 'x' implicitly has an 'any' type
function processData(x) {
return x.value;
}
// ❌ 报错:Argument of type 'string | undefined' is not assignable
function greet(name: string) {
console.log(name.toUpperCase());
}
greet(Math.random() > 0.5 ? "Alice" : undefined);
// ❌ 报错:Property 'name' has no initializer
class User {
name: string;
}
怎么修
方案一:先稳住,再逐步收紧
如果项目较大、暂时没时间处理所有类型问题,先显式写回旧行为:
{
"compilerOptions": {
"strict": false
}
}
方案二:逐项开启(推荐长期方案)
不要一次性开全部,分批处理更容易定位问题:
{
"compilerOptions": {
"noImplicitAny": true
}
}
跑通后继续:
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
最后:
{
"compilerOptions": {
"strict": true
}
}
诊断命令:
# 升级后快速查看报错数量
npx tsc --noEmit 2>&1 | grep "error TS" | wc -l
# 按错误类型统计,优先处理高频问题
npx tsc --noEmit 2>&1 | grep "error TS" | cut -d'(' -f1 | sort | uniq -c | sort -rn | head -20
二、module 默认变成 esnext
变更内容
表格
| 版本 | module 默认值 |
|---|---|
| TS 5.x | commonjs |
| TS 6.0 | esnext |
为什么会报错
如果你还在用 CommonJS 风格的项目(require/module.exports),升级后:
// ❌ 如果是 .ts 文件,require 语法直接报错
const express = require('express');
module.exports = router;
// ❌ 即使配置了,混合语法在 esnext 模块下更容易出问题
const fs = require('fs'); // ❌
import fs from 'fs'; // ✅
怎么修
方案一:迁移到 ESM
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler"
}
}
同时在 package.json 声明:
{
"type": "module"
}
方案二:保持 CommonJS(保守方案)
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "nodenext"
}
}
方案三:渐进式迁移(推荐)
先用 esnext 模块但保持 moduleResolution: "bundler",这是现代前端项目的主流组合:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
三、types 默认变成空数组
变更内容
表格
| 版本 | types 默认值 |
|---|---|
| TS 5.x | 自动扫描 node_modules/@types 全部包 |
| TS 6.0 | [](空数组,不再自动引入) |
为什么会报错
这大概是升级 TS 6 后最容易遇到的报错来源。
升级后你会发现:
// ❌ 报错:Cannot find name 'process'
console.log(process.env.NODE_ENV);
// ❌ 报错:Cannot find name 'setTimeout' / 'setInterval'
setTimeout(() => {}, 1000);
// ❌ 报错:Cannot find name 'describe' / 'it' / 'expect'
describe('test', () => {
it('works', () => {
expect(true).toBe(true);
});
});
因为 @types/node、@types/jest 这些包提供的全局类型,TS 6 不再默认导入了。
怎么修
显式声明你需要的类型包:
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
常见项目类型配置参考:
// Node.js 后端项目
{
"compilerOptions": {
"types": ["node"]
}
}
// React + Jest 测试
{
"compilerOptions": {
"types": ["node", "jest", "@testing-library/jest-dom"]
}
}
// Vite 项目
{
"compilerOptions": {
"types": ["vite/client"]
}
}
临时过渡方案(不推荐长期使用):
如果暂时不想一个个加,可以先这样恢复旧行为:
{
"compilerOptions": {
"types": ["*"]
}
}
但这违背了 TS 6 改进的初衷——自动引入所有 @types 会拖慢编译速度,官方说有的项目能快 20-50%。
四、rootDir 默认变成 '.'
变更内容
表格
| 版本 | rootDir 默认值 |
|---|---|
| TS 5.x | 自动推断源码公共目录 |
| TS 6.0 | '.'(tsconfig.json 所在目录) |
为什么会报错
典型的输出目录问题:
project/
├── tsconfig.json
└── src/
└── index.ts
之前你可能期望输出:
dist/index.js
升级后如果没配置 rootDir,输出可能变成:
dist/src/index.js
怎么修
推荐配置:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"include": ["src"]
}
}
或者如果你用的是 Vite/Webpack 等 bundler,已经有清晰的项目结构:
{
"compilerOptions": {
"rootDir": ".",
"outDir": "dist",
"include": ["src"]
}
}
检查方法:升级后跑一次 npx tsc --listEmittedFiles,看输出文件路径是否符合预期。
五、移除 baseUrl 隐式行为
变更内容
baseUrl 不再作为模块解析的查找根目录。
为什么会报错
很多人这样配置路径别名:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@utils/*": ["utils/*"]
}
}
}
然后写:
import { helper } from '@/utils/helper';
升级后 TS 6 会警告:除非你用的是 paths 里明确声明的前缀,否则 baseUrl 不再兜底查找模块。
也就是说,如果你写了 import something from 'someModule.js',TS 5 会尝试在 baseUrl 下找文件,而 TS 6 不会。
怎么修
方案一:把 baseUrl 前缀合并到 paths(推荐)
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@utils/*": ["./src/utils/*"]
}
}
}
方案二:用 catch-all 保留旧行为(不常用)
{
"compilerOptions": {
"paths": {
"*": ["./src/*"],
"@utils/*": ["./src/utils/*"]
}
}
}
实际项目中最常见的修复:
// 旧配置
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
}
}
}
// 新配置
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
注意:路径别名前缀(@/*)必须和 paths 里的键对应。
六、升级实战清单
第一步:升级前检查
# 查看当前 TS 版本和配置
npx tsc --version
# 备份 tsconfig.json
cp tsconfig.json tsconfig.json.bak
第二步:安装 TS 6
npm install typescript@6 --save-dev
第三步:运行编译,统计问题数量
npx tsc --noEmit 2>&1 | tee tsc-errors.log
第四步:临时稳住
如果报错太多,先加过渡配置:
{
"compilerOptions": {
"ignoreDeprecations": "6.0",
"strict": false,
"types": ["*"]
}
}
第五步:逐项修复
按优先级处理:
表格
| 优先级 | 问题 | 修复方案 |
|---|---|---|
| P0 | @types/* 报错 | 显式配置 types |
| P0 | 隐式 any 报错 | 先开 noImplicitAny |
| P1 | rootDir 输出路径问题 | 配置 rootDir + include |
| P1 | baseUrl 路径别名 | 合并前缀到 paths |
| P2 | strict 报错 | 逐步开启各项检查 |
第六步:验证构建
# 本地构建
npm run build
# 检查输出文件
npx tsc --listEmittedFiles
第七步:CI 集成
# GitHub Actions 示例
- name: Type check
run: npx tsc --noEmit
七、TS 7 展望:值得升级的理由
虽然 TS 6 带来了这些迁移成本,但站在更长的时间线上,这次升级是值得的:
1. 性能收益来自 TS 7
TS 7 基于 Go 重写的编译器,官方测试显示有的场景从 22 秒降到 4 秒。TS 6 清理的配置越干净,TS 7 迁移越顺畅。
2. 现代默认值更省心
strict: true是业界最佳实践,越早开启越好esnext模块 +bundler分辨率是主流方向types空数组能显著加快编译和编辑器响应
3. 新特性实用
using/await using:资源管理告别 try/finallyRegExp.escape:正则转义终于有标准 APIMap.getOrInsert:减少 Map 操作样板代码#/子路径导入:路径别名更优雅
4. 生态同步
VS Code 1.119 已经开始全面迁移到 TS 7。新项目建议直接用 TS 6,默认值更现代。
总结
表格
| 变更 | 旧默认值 | 新默认值 | 应对方式 |
|---|---|---|---|
strict | false | true | 逐项开启或先设 false |
module | commonjs | esnext | 保持 commonjs 或迁移 ESM |
types | 全部 @types | [] | 显式声明需要的包 |
rootDir | 自动推断 | '.' | 配置 rootDir + include |
baseUrl | 查找根目录 | 不再兜底 | 前缀合并到 paths |
一句话建议:新项目直接上 TS 6,老项目把升级当个小项目来做,不要只改个版本号。
本文由AI辅助整理