TypeScript 5 引入了重大新特性,改进了类型安全、开发者生产力和代码组织。从标准化的 ECMAScript 装饰器到 const 类型参数、satisfies 运算符增强和模块解析改进。
ECMAScript 装饰器
TypeScript 5 实现了 TC39 Stage 3 装饰器提案,替换了 TypeScript 4.x 的实验性装饰器实现。新装饰器已标准化且不需要 experimentalDecorators 标志。
类装饰器
类装饰器接收类构造函数,可以返回替换它的新类。
// Class decorator: adds metadata and logging
function sealed(target: Function, context: ClassDecoratorContext) {
Object.seal(target);
Object.seal(target.prototype);
console.log(`Class ${String(context.name)} has been sealed`);
}
function withTimestamp<T extends new (...args: any[]) => {}>(
target: T,
context: ClassDecoratorContext
) {
return class extends target {
createdAt = new Date();
};
}
@sealed
@withTimestamp
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User("Alice");
console.log(user.createdAt); // Date object方法装饰器
方法装饰器接收方法函数和上下文信息,可以包装、替换或修改方法。
// Method decorator: logging and timing
function log(
target: Function,
context: ClassMethodDecoratorContext
) {
return function (this: any, ...args: any[]) {
console.log(`Calling ${String(context.name)} with`, args);
const result = target.apply(this, args);
console.log(`${String(context.name)} returned`, result);
return result;
};
}
function memoize(
target: Function,
context: ClassMethodDecoratorContext
) {
const cache = new Map();
return function (this: any, ...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = target.apply(this, args);
cache.set(key, result);
return result;
};
}
class MathService {
@log
@memoize
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}自动访问器字段
accessor 关键字创建带有自动 getter 和 setter 的字段。
// Auto-accessor with decorator
function observable(
target: ClassAccessorDecoratorTarget<any, any>,
context: ClassAccessorDecoratorContext
) {
return {
get(this: any) {
return target.get.call(this);
},
set(this: any, value: any) {
console.log(`${String(context.name)} changed to ${value}`);
target.set.call(this, value);
},
};
}
class Settings {
@observable
accessor theme: string = "dark";
@observable
accessor fontSize: number = 14;
}
const settings = new Settings();
settings.theme = "light"; // logs: "theme changed to light"const 类型参数
类型参数上的 const 修饰符告诉 TypeScript 推断最具体的(字面量)类型,而不是扩宽到基本类型。
// Without const: types are widened
function createRoutes<T extends Record<string, string>>(routes: T): T {
return routes;
}
const routes1 = createRoutes({ home: "/", about: "/about" });
// Type: { home: string; about: string } (widened!)
// With const: literal types preserved
function createRoutes2<const T extends Record<string, string>>(routes: T): T {
return routes;
}
const routes2 = createRoutes2({ home: "/", about: "/about" });
// Type: { readonly home: "/"; readonly about: "/about" } (exact!)
// Practical example: type-safe event system
function defineEvents<const T extends Record<string, (...args: any[]) => void>>(
events: T
): T {
return events;
}
const events = defineEvents({
click: (x: number, y: number) => {},
keypress: (key: string) => {},
scroll: (offset: number) => {},
});
// events.click is typed as (x: number, y: number) => void
// Object.keys(events) preserves "click" | "keypress" | "scroll"
// Array literal types
function firstElement<const T extends readonly unknown[]>(arr: T): T[0] {
return arr[0];
}
const first = firstElement([1, "hello", true] as const);
// Type: 1 (not number)satisfies 运算符增强
satisfies 运算符验证表达式匹配类型而不更改推断的类型,同时提供类型检查和精确推断。
// Problem: type annotation loses specificity
type Colors = Record<string, string | number[]>;
const colors1: Colors = {
red: "#ff0000",
green: [0, 255, 0],
};
// colors1.red is string | number[] -- lost specificity!
// Solution: satisfies preserves inferred types
const colors2 = {
red: "#ff0000",
green: [0, 255, 0],
} satisfies Colors;
colors2.red.toUpperCase(); // OK! TypeScript knows it's a string
colors2.green.map(x => x); // OK! TypeScript knows it's number[]
// satisfies catches errors while preserving types
const config = {
port: 3000,
host: "localhost",
debug: true,
// timeout: "not a number", // Error! Does not satisfy Config type
} satisfies {
port: number;
host: string;
debug: boolean;
};
// config.port is number (not string | number)
// config.debug is true (not boolean)模块解析:bundler 模式
TypeScript 5 引入了名为 "bundler" 的新模块解析模式,匹配 Vite、Webpack 等现代打包器解析模块的方式。
// tsconfig.json for bundler projects (Vite, Webpack)
{
"compilerOptions": {
"moduleResolution": "bundler",
"module": "esnext",
"target": "esnext",
"noEmit": true,
// Bundler mode supports:
// - Package.json "exports" and "imports" fields
// - Extensionless imports (import "./utils" resolves to "./utils.ts")
// - Directory imports (import "./components" resolves to "./components/index.ts")
// - .ts extension in imports (optional)
}
}
// tsconfig.json for Node.js applications
{
"compilerOptions": {
"moduleResolution": "nodenext",
"module": "nodenext",
// Strict mode: requires file extensions in imports
// import "./utils.js" (not "./utils")
}
}改进的类型收窄
TypeScript 5 在多种场景中改进了类型收窄。
switch(true) 收窄
TypeScript 5 在 switch(true) 模式中正确收窄类型。
// switch(true) narrowing - now works in TypeScript 5
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (true) {
case shape.kind === "circle":
return Math.PI * shape.radius ** 2; // shape is narrowed to circle
case shape.kind === "rect":
return shape.width * shape.height; // shape is narrowed to rect
case shape.kind === "triangle":
return (shape.base * shape.height) / 2; // shape is narrowed to triangle
}
}性能改进
TypeScript 5 全面提供显著的性能改进。
- 包大小减少 44%(从 63.8MB 到 35.6MB)
- 大多数项目构建时间改进 10-25%
- 编辑器响应性提高
- 通过更好的内部数据结构减少内存使用
- 优化模块解析缓存加快项目加载
新配置选项
TypeScript 5 引入了多个新的 tsconfig.json 选项。
// tsconfig.json - recommended TypeScript 5 settings
{
"compilerOptions": {
// New in TS 5
"moduleResolution": "bundler", // For Vite/Webpack projects
"verbatimModuleSyntax": true, // Replaces importsNotUsedAsValues
"allowImportingTsExtensions": true, // Allow .ts imports (with noEmit)
// Recommended strict settings
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Modern output
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Other
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"jsx": "react-jsx"
}
}迁移到 TypeScript 5
对大多数项目来说,升级到 TypeScript 5 是简单的。
破坏性变更
- 运行时要求:TypeScript 5 需要 Node.js 14.17 或更高版本
- 已移除弃用选项:out、charset 等
- lib.d.ts 变更:DOM 类型定义已更新
- 装饰器:新标准装饰器具有不同的语义
# Upgrade to TypeScript 5
npm install typescript@latest
# Check for issues without emitting
npx tsc --noEmit
# If using deprecated options, remove them from tsconfig.json
# "out" -> use "outDir" instead
# "keyofStringsOnly" -> removed, no replacement needed
# Run your test suite to catch any runtime issues
npm test常见问题
需要为 TypeScript 5 修改装饰器代码吗?
不需要立即修改。TypeScript 5 仍支持旧的 experimentalDecorators。但建议新代码使用新标准装饰器。
TypeScript 5 向后兼容吗?
基本兼容。主要破坏性变更是移除弃用的编译器选项和 lib.d.ts 更新。
satisfies 和 as 有什么区别?
as 断言类型并改变推断类型。satisfies 验证类型但保留原始推断类型。
应该使用 bundler 模块解析吗?
对于使用 Vite 或 Webpack 的前端项目,是的。对于 Node.js 应用使用 node16。
const 类型参数和 as const 有什么区别?
const 修饰符使泛型函数自动推断字面量类型,无需调用者添加 "as const"。