为什么要重写编译器
TypeScript 正在经历诞生以来最大的一次底层变革:官方把编译器从自举的 TypeScript/JavaScript 移植到了 Go,代号 tsgo(TypeScript Native)。原因很现实——tsc 与 VS Code 的语言服务跑在 V8 上,单线程、GC 压力大,在中大型仓库里一次全量检查动辄几十秒,编辑器跳转与补全也会随项目规模线性变慢。改用 Go 之后,可以天然利用多核并行、更紧凑的内存布局和更低的 GC 开销,官方给出的目标是编辑器启动与类型检查提速一个数量级,同时在语言层面保持 100% 兼容。
快速体验原生编译器
原生预览版以 npm 包形式分发,无需改动现有 tsconfig.json:
# 安装原生预览编译器
npm i -D @typescript/native-preview
# 类型检查(等价于 tsc --noEmit)
npx tsgo --noEmit
# 增量构建/工程引用
npx tsgo --build
# 监听模式
npx tsgo --noEmit --watch在 VS Code 中可以安装 TypeScript Native 预览插件,或在设置里开启实验性选项,让编辑器后台直接使用 Go 版语言服务。CI 上的收益最直观:把 tsc --noEmit 换成 tsgo --noEmit,很多仓库的流水线时间会直接砍半。
现代 TS 里最值得掌握的三个类型特性
1. satisfies:既校验结构,又保留字面量类型
type Route = { path: string; method: 'GET' | 'POST' };
const routes = {
home: { path: '/', method: 'GET' },
login: { path: '/login', method: 'POST' },
} satisfies Record<string, Route>;
routes.home.method;
// 类型收窄为 'GET',而不是宽泛的 'GET' | 'POST'satisfies 解决了“用类型标注会丢失字面量推断,不用标注又无法校验”的两难,是配置表、路由表、主题表的最佳拍档。
2. const 类型参数:让泛型默认按字面量推断
function tuple<const T extends readonly unknown[]>(...args: T): T {
return args;
}
const t = tuple(1, 'a', true);
// 推断为 readonly [1, "a", true],而不是 (string | number | boolean)[]以前要拿到元组类型必须写 as const,现在库作者可以直接在函数签名层面“默认 const”。
3. NoInfer:阻止无关位置的泛型推断
function createStore<T>(initial: T, update: (v: NoInfer<T>) => T) {
let value = initial;
return { get: () => value, set: (v: NoInfer<T>) => (value = update(v)) };
}
const store = createStore({ count: 0 }, (prev) => ({ count: prev.count + 1 }));NoInfer<T> 让第二个参数只做“检查”不参与推断,避免推断结果被意外 widen。
类型体操实战:类型安全的对象路径
下面这套递归类型可以为任意嵌套对象生成合法路径联合类型,常用于表单校验、国际化键名和状态选择器:
type Path<T> = T extends object
? {
[K in keyof T & string]: T[K] extends object
? K | `${K}.${Path<T[K]>}`
: K;
}[keyof T & string]
: never;
type User = { id: number; profile: { name: string; city: string } };
// "id" | "profile" | "profile.name" | "profile.city"
type UserPath = Path<User>;
function getByPath<T extends object, K extends Path<T>>(obj: T, path: K): unknown {
return path.split('.').reduce<any>((acc, key) => acc?.[key], obj);
}
getByPath({ id: 1, profile: { name: 'Ada', city: 'London' } } as User, 'profile.city');
// getByPath(user, 'profile.age') -> 编译报错迁移建议与踩坑
- 先并行跑:CI 里同时执行
tsc --noEmit与tsgo --noEmit,对比诊断输出,确认无误后再切换。 - 插件生态:依赖 TS 内部 API 的自定义 transformer、旧版 lint 插件仍需要 JS 版编译器,建议留在灰度阶段。
- 渐进收紧:趁提速后有余量,把
strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes全部打开,类型体操才真正有价值。
小结
tsgo 的意义不只是“更快”,它让类型系统从“拖慢开发”变成“可以放心加码”。配合 satisfies、const 类型参数与 NoInfer 这些现代特性,TypeScript 正在把 JS 生态的开发体验推向一个新的台阶。
评论 0
还没有评论,来说两句吧。