DevToolBox免费
博客

TypeScript 5 新特性:装饰器、const 类型参数与 satisfies 运算符

12 分钟作者 DevToolBox

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"。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

🔷JSON to TypeScript OnlineTS→TypeScript to JavaScript Converter{ }JSON Formatter

相关文章

TypeScript 类型守卫:运行时类型检查完全指南

掌握 TypeScript 类型守卫:typeof、instanceof、in、自定义类型守卫和可辨识联合。

Zod 验证指南:Schema、转换、精细化与 tRPC 集成

TypeScript 中 Zod 模式验证完全指南:定义 schema、数据转换、自定义精细化与 tRPC 集成。