TypeScript 5.x 里真正改变写法的特性
装饰器标准化、const 类型参数、satisfies 与 using —— 挑出那些会实际改变你日常代码的更新。
TypeScript 每年都会发布一堆特性,其中大部分你永远不会用。这一篇只挑四个真正会改变日常写法的。
1. satisfies:既要校验,又要保留字面量
在 satisfies 出现之前,你只有两个选择:要么用类型注解(丢失字面量),要么用 as(丢失校验)。
type Route = { path: string; auth: boolean };
// 注解:config 的类型被拓宽成 Route,keys 丢了
const config: Record<string, Route> = {
home: { path: '/', auth: false },
};
// satisfies:既校验,又保留精确类型
const routes = {
home: { path: '/', auth: false },
admin: { path: '/admin', auth: true },
} satisfies Record<string, Route>;
routes.admin.path; // 类型是 '/admin',不是 string
这在配置对象、主题 token、路由表里几乎是必备的。
2. const 类型参数:让泛型推断更“贪心”
泛型默认会把字面量拓宽成基础类型。加一个 const 修饰符,就能让推断保留字面量:
declare function defineRoutes<const T extends readonly string[]>(routes: T): T;
const r = defineRoutes(['home', 'posts', 'about']);
// 类型是 readonly ["home", "posts", "about"],而不是 string[]
写库的人受益最大,但业务代码里定义枚举式的常量集合时也很好用。
3. 标准装饰器:终于不用两套写法了
TypeScript 5.0 实现了 TC39 的装饰器提案,与 experimentalDecorators 是两套东西。新的装饰器有明确的执行顺序和更好的类型支持:
function logged<This, Args extends unknown[], Ret>(
target: (this: This, ...args: Args) => Ret,
context: ClassMethodDecoratorContext
) {
return function (this: This, ...args: Args): Ret {
console.log(`→ ${String(context.name)}`, args);
return target.apply(this, args);
};
}
class Service {
@logged
fetch(id: string) {
return db.get(id);
}
}
迁移时要注意:新装饰器不能修改 prototype,也不能装饰参数。老代码需要重写,不是换个开关就行。
4. using:显式资源管理
using 声明会在作用域结束时自动调用 [Symbol.dispose](),await using 则调用 [Symbol.asyncDispose]():
function withTempDir() {
using dir = openTempDir(); // 离开作用域时自动清理
return process(dir.path);
}
async function query() {
await using conn = await pool.acquire();
return conn.execute('select 1');
}
对比 try/finally,它的价值在于清理逻辑跟着资源定义走,而不是散落在每个调用点。
不值得追的那些
顺便说一句,下面这些我基本不用:
- 装饰器元数据(
emitDecoratorMetadata):和框架强绑定,迁移成本高 namespace:ESM 已经够用- 过度嵌套的条件类型:三个月后你自己也读不懂
工具的价值在于减少认知负担。任何让代码更难读的特性,即使它“更类型安全”,也应该谨慎引入。