TS 5.x 新特性深度解读
TS 5.x 新特性深度解读
写在前面
前面七篇讲了 TS 类型系统的核心。这一篇讲 TS 5.x——过去两年 TypeScript 发布的所有重要新特性。
5.x 不是一个版本,是一系列版本——5.0、5.1、5.2、5.3、5.4、5.5、5.6,每个版本都带来新东西。本篇挑最实用、最有影响力的讲:satisfies 操作符、const 类型参数、using 资源管理、decorator 标准化等。
一、TS 5.0 - 重大新特性
1.1 const type parameter
// TS 5.0 之前 - 类型参数推断是 string
function fn<const T>(value: T): T {
return value;
}
const s = fn("hello"); // s 是 "hello"(字面量类型)
// 之前是 string
const 类型参数告诉 TS:尽量推断为字面量类型,不要泛化。
实战:对象字面量
function makeConfig<const T extends object>(config: T): T {
return config;
}
const config = makeConfig({
endpoint: "/api/users", // 不加 const 是 string,加了是 "/api/users"
timeout: 5000, // 不加 const 是 number,加了是 5000
});
// 用法 - 类型是字面量
config.endpoint; // 类型是 "/api/users"
1.2 satisfies 操作符(重要)
// TS 5.0 新增
type Color = "red" | "green" | "blue";
type RGB = [number, number, number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<Color, RGB | string>;
// ^^^^^^^^
// satisfies 检查合规,但保留具体类型
satisfies 的作用:
- 检查值是否符合某个类型
- 保留值的具体类型(不收窄)
对比 as 和 :
// 1. 直接注解 - 类型被收窄
const colors: Record<Color, RGB | string> = {
red: [255, 0, 0],
green: "#00ff00",
};
// 之后 colors.red 是 RGB | string
// 2. as 断言 - 不检查,强制转换
const colors = {
red: [255, 0, 0],
green: "#00ff00",
} as Record<Color, RGB | string>;
// 之后 colors.red 是 RGB | string(可能不安全)
// 3. satisfies - 检查 + 保留
const colors = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<Color, RGB | string>;
// 之后 colors.red 是 [number, number, number] (具体类型保留)
// 但如果漏了 blue,会编译报错
satisfies 比 : 严格,比 as 安全。
实战:配置对象
// 配置 schema
const Routes = {
home: "/",
users: "/users",
userDetail: "/users/:id",
} satisfies Record<string, string>;
// 用法 - 类型仍然是字面量
type HomeRoute = typeof Routes.home; // "/"
function navigate(route: HomeRoute) { ... }
navigate(Routes.home); // OK
navigate(Routes.users); // 报错 - users 是 "/users",不是 "/"
// 但漏字段会报错
const BadRoutes = {
home: "/",
users: "/users",
// 缺 userDetail
} satisfies Record<string, string>;
// 报错 - 缺少 userDetail
1.3 多重 extends
// TS 5.0 之前
type T = A & B & C; // 合并
// TS 5.0 - 可以做元组式合并
type T = A & (B & C);
// 等同上面,但可以用于某些场景
二、TS 5.2 - using 资源管理
2.1 资源管理的痛点
传统 try/finally 写起来繁琐:
{
const file = await openFile("data.txt");
try {
const data = await file.read();
return process(data);
} finally {
await file.close();
}
}
2.2 using 关键字
{
await using file = await openFile("data.txt");
// 用 file
const data = await file.read();
return process(data);
} // 自动 close(file)
await using 在作用域结束时自动调用 [Symbol.asyncDispose]。
2.3 实战:数据库连接
class DatabaseConnection {
[Symbol.asyncDispose] = async () => {
await this.close();
};
}
async function getUser(id: string) {
await using db = new DatabaseConnection();
return db.query("SELECT * FROM users WHERE id = $1", [id]);
}
// db 自动关闭
2.4 sync 版本(dispose)
{
using file = openFileSync("data.txt");
// 用 file
} // 自动 close
using 同步版本——Symbol.dispose 在作用域结束时调用。
2.5 实战:锁管理
class Lock {
[Symbol.dispose] = () => {
this.unlock();
};
}
{
using _lock = acquireLock("resource");
// 临界区代码
// 锁自动释放
}
三、TS 5.0 - decorator 标准化
3.1 旧的 experimental decorator
// TS 5.0 之前的实验性 decorator
@Component({ selector: "app-root" })
class AppComponent {
@Input() name: string;
@Output() click = new EventEmitter();
}
问题:实现不规范,跟 TC39 标准不一致。
3.2 新的标准化 decorator
// TS 5.0+ 标准化 decorator
function logged<This, Args extends any[]>(
originalMethod: (this: This, ...args: Args) => any,
context: ClassMethodDecoratorContext
) {
return function (this: This, ...args: Args) {
console.log(`Calling ${String(context.name)}`);
return originalMethod.apply(this, args);
};
}
class MyClass {
@logged
greet(name: string) {
return `Hello, ${name}`;
}
}
new MyClass().greet("Alice");
// "Calling greet"
// "Hello, Alice"
3.3 实战:自动 binding
function bound<This, Args extends any[]>(
_: (this: This, ...args: Args) => any,
context: ClassMethodDecoratorContext<This>
) {
const methodName = context.name;
context.addInitializer(function () {
(this as any)[methodName] = (this as any)[methodName].bind(this);
});
}
class Button {
@bound
handleClick() {
console.log(this); // this 永远指向实例
}
}
const btn = new Button();
const fn = btn.handleClick;
fn(); // this 是 btn,不是 undefined
3.4 实战:自动 memoize
function memoize<This, Args extends any[], R>(
originalMethod: (this: This, ...args: Args) => R,
context: ClassMethodDecoratorContext<This>
) {
const cache = new Map<string, R>();
return function (this: This, ...args: Args): R {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key)!;
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
}
class Calculator {
@memoize
fibonacci(n: number): number {
if (n < 2) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
3.5 实战:依赖注入
function inject<T>(token: new (...args: any[]) => T) {
return function (
_: any,
context: ClassFieldDecoratorContext
) {
context.addInitializer(function () {
(this as any)[context.name] = container.resolve(token);
});
};
}
class UserService {
@inject(DatabaseService)
db!: DatabaseService;
getUser(id: string) {
return this.db.query(...);
}
}
四、TS 5.x 其他重要新特性
4.1 verbatimModuleSyntax(TS 5.0)
// tsconfig.json
{
"compilerOptions": {
"verbatimModuleSyntax": true
}
}
强制显式 import type——让 esbuild / swc 能正确处理类型导入。
// NG - 这种写法有问题(运行时也会 import)
import { SomeType } from "./types";
// OK - 用 import type
import type { SomeType } from "./types";
// 混用
import { someValue, type SomeType } from "./module";
配合 isolatedModules: true 必须开——否则有些场景类型会被错误删除。
4.2 索引访问类型的优化(TS 5.x)
// TS 5.x 之前
type T = { [K in keyof { a: 1; b: 2 }]: K }[keyof { a: 1; b: 2 }];
// T 是 "a" | "b"(以前不稳定)
// TS 5.x 之后稳定
type T = keyof { a: 1; b: 2 }; // "a" | "b"
4.3 命名空间支持 indexed access(TS 5.x)
namespace MyNamespace {
export const a = 1;
export const b = 2;
}
type Keys = keyof typeof MyNamespace; // "a" | "b"
type A = (typeof MyNamespace)["a"]; // 1
4.4 type predicates 缩窄(TS 5.x)
function isString(value: unknown): value is string {
return typeof value === "string";
}
const x: unknown = "hello";
if (isString(x)) {
// TS 5.x 改进: x 进一步缩窄为 string
x.toUpperCase();
}
4.5 精确可选属性类型(TS 4.4+,5.x 推荐)
{
"compilerOptions": {
"exactOptionalPropertyTypes": true
}
}
interface User {
name: string;
age?: number;
}
const u1: User = { name: "Alice", age: undefined }; // 报错
const u2: User = { name: "Alice" }; // OK
const u3: User = { name: "Alice", age: 30 }; // OK
4.6 NoInfer utility(TS 5.4+)
// 不希望被推断的参数
function createFSM<S extends string>(states: S[], initial: NoInfer<S>) { ... }
createFSM(["idle", "running"], "idle"); // OK
createFSM(["idle", "running"], "paused"); // 报错 - "paused" 不在 states 中
4.7 Object.groupBy / Map.groupBy(TS 5.4+ lib)
const arr = [
{ type: "a", value: 1 },
{ type: "b", value: 2 },
{ type: "a", value: 3 },
];
Object.groupBy(arr, item => item.type);
// { a: [...], b: [...] }
五、TS 5.x 的迁移注意事项
5.1 升级 TS 版本
npm install -D typescript@latest
升级前:
- 看 Breaking Changes ——每个 TS 版本都有少量 breaking change
- 逐版本升级——不要从 TS 4.x 直接到 TS 5.5
- 先看 IDE 警告——vscode TS 插件会提示
5.2 常见 Breaking Changes
TS 5.0
- 限制 lib 引用 ——一些老 lib 文件被删除
- 装饰器语义变化 ——标准装饰器和老的实验装饰器不兼容
TS 5.4
Object.groupBy行为微调- 一些隐式 any 推断更严
5.3 实战:我们项目的 TS 升级
公司项目从 TS 4.9 升到 TS 5.6:
| 步骤 | 耗时 | 问题数 |
|---|---|---|
| 4.9 → 5.0 | 1 周 | 3(都是装饰器) |
| 5.0 → 5.2 | 2 周 | 5(主要是 decorator 重写) |
| 5.2 → 5.4 | 1 天 | 0 |
| 5.4 → 5.6 | 1 天 | 1(unknown 收窄变严) |
半年完成升级,没有任何线上问题。
六、实战:用 satisfies 重构配置
6.1 重构前
interface Config {
api: { baseUrl: string; timeout: number };
features: Record<string, boolean>;
}
const config: Config = {
api: { baseUrl: "https://api.example.com", timeout: 5000 },
features: { newUI: true, betaSearch: false },
};
// 问题:访问 config.api.baseUrl 是 string(被类型收窄)
// 但实际是字面量 "https://api.example.com"
6.2 用 satisfies 重构
const config = {
api: { baseUrl: "https://api.example.com", timeout: 5000 },
features: { newUI: true, betaSearch: false },
} satisfies Config;
// config.api.baseUrl 是 "https://api.example.com"(字面量保留)
// 但 TS 仍然检查:漏字段会报错,类型不对会报错
// 实际好处:可以用在 URL 模板
type ApiBaseUrl = typeof config.api.baseUrl; // "https://api.example.com"
6.3 实战:i18n 资源
const translations = {
common: {
hello: "你好",
bye: "再见",
},
user: {
profile: {
title: "个人资料",
edit: "编辑",
},
},
} satisfies Record<string, Record<string, any>>;
// translations.common.hello 是 "你好"(字面量)
// 漏字段会报错
七、实战:用 using 简化资源管理
7.1 实战 1:HTTP 请求上下文
class RequestContext {
[Symbol.dispose] = () => {
console.log(`Request done, duration: ${Date.now() - this.start}ms`);
};
constructor(public start: number = Date.now()) {}
}
async function handleRequest(req: Request) {
using _ctx = new RequestContext();
// 业务逻辑
return processRequest(req);
}
// 请求结束自动记录日志
7.2 实战 2:性能计时
class Timer {
[Symbol.dispose] = () => {
console.log(`${this.label}: ${Date.now() - this.start}ms`);
};
constructor(private label: string, private start: number = Date.now()) {}
}
async function heavyOperation() {
using _t = new Timer("heavyOperation");
// 业务
}
// 自动打印耗时
7.3 实战 3:多资源管理
async function transfer(from: Account, to: Account, amount: number) {
await using fromLock = new AsyncLock(from);
await using toLock = new AsyncLock(to);
// LIFO 顺序释放(后获取的先释放)
from.withdraw(amount);
to.deposit(amount);
}
// 两个锁自动释放
八、实战:用 decorator 做日志
8.1 简单日志 decorator
function log<This, Args extends any[], R>(
originalMethod: (this: This, ...args: Args) => R,
context: ClassMethodDecoratorContext<This, R>
) {
return function (this: This, ...args: Args): R {
console.log(`[${String(context.name)}] called with`, args);
const result = originalMethod.apply(this, args);
console.log(`[${String(context.name)}] returned`, result);
return result;
};
}
class UserService {
@log
async getUser(id: string): Promise<User> {
return db.user.findUnique({ where: { id } });
}
}
await userService.getUser("123");
// [getUser] called with ["123"]
// [getUser] returned User {...}
8.2 性能监控 decorator
function measureTime<This, Args extends any[], R>(
originalMethod: (this: This, ...args: Args) => R | Promise<R>,
context: ClassMethodDecoratorContext<This, R>
) {
return async function (this: This, ...args: Args): Promise<R> {
const start = performance.now();
try {
return await Promise.resolve(originalMethod.apply(this, args));
} finally {
const duration = performance.now() - start;
metrics.histogram(`method.${String(context.name)}.duration`, duration);
}
};
}
class OrderService {
@measureTime
async createOrder(input: CreateOrderInput): Promise<Order> {
// 业务逻辑
}
}
8.3 实战:权限检查 decorator
function requireRole(role: "user" | "admin" | "superadmin") {
return function (
originalMethod: (this: any, ...args: any[]) => any,
context: ClassMethodDecoratorContext
) {
return function (this: any, ...args: any[]) {
if (!this.user || !hasRole(this.user, role)) {
throw new Error(`Permission denied: ${role} required`);
}
return originalMethod.apply(this, args);
};
};
}
class AdminController {
user: User;
@requireRole("admin")
deleteUser(id: string) {
// 只有 admin 能调
}
@requireRole("superadmin")
deleteOrg(orgId: string) {
// 只有 superadmin 能调
}
}
九、TS 5.x 的局限与展望
9.1 还没解决的
- 没有 match/case ——Python/Rust 的模式匹配 TS 还没有
- 没有内置 Effect 系统 ——async/await 的资源管理不如 Koka / Scala
- 运行时类型 ——类型运行时不存在,需要 zod 补充
9.2 未来可能加入
- 更精确的 inference ——TS 编译器团队一直在改进
- 更好的 error message ——尤其是泛型错误
- 原生 ESM 改进 ——Node ESM 还在演进
- Project Go ——TS 编译器用 Go 重写,性能 10 倍提升(2025+ 启动)
9.3 TS 编译器的 Go 重写
Microsoft 在 2024 年宣布 Project Corsa——用 Go 重写 TS 编译器:
- 冷启动时间 10 秒 → 0.1 秒
- 类型检查 5x 加速
- 内存占用 减半
2026 年初已经在 VS Code 里集成 preview。
十、本篇小结
| 特性 | 版本 | 应用 |
|---|---|---|
satisfies |
5.0 | 配置对象,保留字面量类型 |
const 类型参数 |
5.0 | 推断为字面量 |
using / await using |
5.2 | 资源自动释放 |
| 标准化 decorator | 5.0 | 日志、权限、DI |
verbatimModuleSyntax |
5.0 | 强制显式 import type |
NoInfer |
5.4 | 控制泛型推断范围 |
| 实战 | 收益 |
|---|---|
| satisfies 配置 | 类型严格 + 字面量保留 |
| using 资源 | 代码量减半,资源安全 |
| decorator | 切面编程(AOP) |
| TS 升级 | 持续吃新特性 |
下一篇是 TS 系列的最后一篇,Monorepo 与类型共享——把内部 SDK 的类型发布流程做到极致。