前端框架 精选推荐

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

HTMLPAGE 团队
16 分钟阅读

深入解析 TypeScript 5 系列的重要更新,包括标准装饰器、const 类型参数、satisfies 运算符等实用特性

#TypeScript 5 #装饰器 #类型系统 #前端工程化

TypeScript 5 系列的演进

TypeScript 5.0 在 2023 年发布后,后续的 5.1、5.2、5.3、5.4 版本持续带来实用改进。这些更新聚焦于两个方向:让类型系统更强大让开发体验更流畅

本文汇总 5.x 系列最值得关注的特性,帮助你把握 TypeScript 的演进脉络。

标准装饰器终于来了

漫长的等待

JavaScript 装饰器提案从 2014 年开始,历经多次修改,终于在 Stage 3 稳定。TypeScript 5.0 实现了这一标准版本。

之前 TypeScript 的装饰器需要 experimentalDecorators 标志,语法和标准有差异。现在可以使用符合 ECMAScript 标准的装饰器。

类装饰器

function logged(target: new (...args: any[]) => any, context: ClassDecoratorContext) {
  return class extends target {
    constructor(...args: any[]) {
      console.log(`Creating instance of ${context.name}`)
      super(...args)
    }
  }
}

@logged
class User {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

// 创建实例时会打印日志
const user = new User('Alice')

关键变化:装饰器函数接收 context 参数,包含被装饰元素的元信息。

方法装饰器

function timing<This, Args extends any[], Return>(
  target: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext
) {
  return function (this: This, ...args: Args): Return {
    const start = performance.now()
    const result = target.call(this, ...args)
    const duration = performance.now() - start
    console.log(`${String(context.name)} took ${duration}ms`)
    return result
  }
}

class Calculator {
  @timing
  complexCalculation(n: number): number {
    // 复杂计算...
    return result
  }
}

属性装饰器

function observable(
  target: undefined,
  context: ClassFieldDecoratorContext
) {
  return function (initialValue: unknown) {
    console.log(`Initializing ${String(context.name)} with`, initialValue)
    return initialValue
  }
}

class Settings {
  @observable
  theme = 'dark'
}

与旧版装饰器的差异

特性旧版 (experimentalDecorators)新版 (标准)
参数装饰器✅ 支持❌ 不支持
属性描述符✅ 可访问❌ 不可访问
context 对象❌ 无✅ 有
返回值替换原定义包装原定义
标准化非标准ECMAScript 标准

迁移建议:新项目用标准装饰器,旧项目如果大量使用参数装饰器(如 NestJS),暂时保持旧版。

const 类型参数

问题背景

TypeScript 推断字面量类型时,默认会"拓宽":

function getConfig<T>(config: T) {
  return config
}

const config = getConfig({ readonly: true, timeout: 5000 })
// 类型被推断为 { readonly: boolean, timeout: number }
// 而不是 { readonly: true, timeout: 5000 }

之前的解决方案是使用 as const

const config = getConfig({ readonly: true, timeout: 5000 } as const)

但这需要调用者主动加,库作者无法强制。

const 类型参数

TypeScript 5.0 允许在类型参数上声明 const

function getConfig<const T>(config: T) {
  return config
}

const config = getConfig({ readonly: true, timeout: 5000 })
// 自动推断为 { readonly: true, timeout: 5000 }

调用者不需要改任何代码,类型自动是字面量类型。

实用场景

路由定义

function createRouter<const T extends readonly Route[]>(routes: T) {
  return routes
}

const router = createRouter([
  { path: '/', component: Home },
  { path: '/about', component: About }
])
// path 被推断为 '/' | '/about' 而不是 string

配置验证

function defineConfig<const T extends ConfigSchema>(config: T) {
  return config
}

const config = defineConfig({
  mode: 'production',
  features: ['auth', 'analytics']
})
// mode 是 'production',features 是 readonly ['auth', 'analytics']

satisfies 运算符

类型断言的困境

type ColorMap = Record<string, string | number[]>

const colors: ColorMap = {
  red: '#ff0000',
  green: [0, 255, 0]
}

// 问题:colors.red 的类型是 string | number[]
// 即使我们知道它是 string
colors.red.toUpperCase() // 报错!

用类型注解保证了符合 ColorMap 结构,但丢失了更精确的类型信息。

satisfies 的解法

const colors = {
  red: '#ff0000',
  green: [0, 255, 0]
} satisfies ColorMap

// colors.red 的类型是 string
colors.red.toUpperCase() // 正常!

// colors.green 的类型是 number[]
colors.green.map(x => x * 2) // 正常!

satisfies 验证值符合某个类型,但保留推断出的更精确类型。

典型使用场景

配置对象

type ThemeConfig = {
  colors: Record<string, string>
  spacing: Record<string, number>
}

const theme = {
  colors: {
    primary: '#3b82f6',
    secondary: '#6b7280'
  },
  spacing: {
    sm: 8,
    md: 16,
    lg: 24
  }
} satisfies ThemeConfig

// theme.colors.primary 是 string(不是 unknown)
// theme.spacing.sm 是 number

枚举映射

type Fruit = 'apple' | 'banana' | 'orange'

const fruitPrices = {
  apple: 1.5,
  banana: 0.8,
  orange: 2.0
} satisfies Record<Fruit, number>

// 如果漏掉某个水果,会报错
// 同时 fruitPrices.apple 的类型是 number

其他值得关注的更新

推断类型窄化

TypeScript 5.1 改进了控制流分析:

function processValue(value: string | number) {
  if (typeof value === 'string') {
    return value // 这里 value 是 string
  }
  return value // 这里 value 自动窄化为 number
}

以前的版本在某些复杂场景下窄化不够精确,5.1+ 显著改善。

更好的 JSDoc 支持

TypeScript 5.x 改进了 JSDoc 解析:

/**
 * @template {string} T
 * @param {T} value
 * @returns {Uppercase<T>}
 */
function toUpper(value) {
  return value.toUpperCase()
}

JavaScript 项目也能享受类型检查的好处。

Import Attributes

支持新的导入属性语法:

import config from './config.json' with { type: 'json' }

这是 ECMAScript 的新提案,TypeScript 5.3 开始支持。

switch (true) 窄化

function getLength(value: string | string[]) {
  switch (true) {
    case typeof value === 'string':
      return value.length // value 是 string
    case Array.isArray(value):
      return value.length // value 是 string[]
  }
}

之前这种模式下类型窄化不工作,5.3+ 修复。

正则命名捕获组推断

const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

const match = '2024-01-15'.match(datePattern)
if (match?.groups) {
  // groups.year, groups.month, groups.day 都有正确类型
  const { year, month, day } = match.groups
}

TypeScript 5.4 能从正则字面量推断捕获组的名称。

性能改进

编译速度

TypeScript 5.0 重构了内部数据结构,带来显著性能提升:

指标4.95.0提升
项目加载时间100%85%15%
增量编译100%75%25%
内存占用100%90%10%

包大小

TypeScript npm 包体积减少约 40%,安装更快。

迁移与升级

升级步骤

  1. 更新依赖npm install typescript@latest
  2. 检查 tsconfig:某些选项在新版本有变化
  3. 运行类型检查npx tsc --noEmit
  4. 修复新发现的错误:新版本类型检查更严格,可能发现之前漏掉的问题

常见问题

Q:装饰器需要修改配置吗? A:使用标准装饰器不需要额外配置。使用旧版装饰器仍需 experimentalDecorators: true

Q:satisfies 会影响运行时吗? A:不会,satisfies 纯粹是类型层面的,编译后不存在。

Q:const 类型参数有性能影响吗? A:对编译器有微小影响,运行时完全没有。

最佳实践建议

逐步采用新特性

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true
    // 不需要特殊配置即可使用大多数新特性
  }
}

类型定义优先级

  1. 推断:能推断就不写
  2. satisfies:需要验证类型但保留推断
  3. 类型注解:需要明确指定类型
  4. as 断言:只在必要时使用

装饰器选择

// 新项目:使用标准装饰器
@logged
class NewService {}

// 依赖 NestJS/Angular:保持旧版装饰器
// tsconfig.json: experimentalDecorators: true
@Injectable()
class LegacyService {}

总结

TypeScript 5.x 的核心价值:

特性解决的问题
标准装饰器与 ECMAScript 标准对齐
const 类型参数库作者控制字面量推断
satisfies类型验证与精确推断兼得
类型窄化改进减少不必要的类型断言
性能优化更快的编译速度

TypeScript 团队坚持"实用主义"路线:不追求类型系统的理论完备性,而是解决开发者的实际痛点。5.x 系列的每个更新都体现了这一理念。


相关文章推荐: