文章索引

satisfies、as const 与类型收窄的日常

· 2 分钟

satisfies 出现之前,我们在「类型标注」和「类型推断」之间只能二选一。标注了就丢失精度,不标注就没有校验。它的价值在于两个都要。

场景一:配置对象

先看没有它的时候会发生什么:

ts
type Route = { path: string, prerender?: boolean }

// 标注:校验通过,但 routes.home.path 的类型退化成 string
const routes: Record<string, Route> = {
  home: { path: '/', prerender: true },
  posts: { path: '/posts' },
}

// 推断:保住了字面量类型,但拼错字段没人管
const routes2 = {
  home: { path: '/', prerennder: true }, // 拼错了,无人报错
}

satisfies 同时拿到两边的好处:

ts
const routes = {
  home: { path: '/', prerender: true },
  posts: { path: '/posts' },
} satisfies Record<string, Route>

routes.home.path // 类型是 '/',不是 string
routes.home.prerennder // 编译期报错

场景二:和 as const 叠加

as const 负责把值冻结成最窄的字面量类型,satisfies 负责校验形状,两者叠加是常见组合:

ts
const themes = ['dark', 'light'] as const satisfies readonly string[]

type Theme = (typeof themes)[number] // 'dark' | 'light'

联合类型直接从数据里长出来,加一个主题只需要改一处。

场景三:穷尽检查

satisfies never 是最便宜的穷尽检查手段:

ts
function label(theme: Theme): string {
  switch (theme) {
    case 'dark': return '暗色'
    case 'light': return '亮色'
    default: {
      const unreachable = theme satisfies never
      throw new Error(`未处理的主题: ${unreachable}`)
    }
  }
}

未来给 themes 加了新值却忘记补 case,这里会在编译期亮红灯,而不是在运行时抛异常。

一条经验法则

写常量数据时先用推断,发现需要校验形状时加 satisfies,需要字面量联合时加 as const。显式标注留给函数签名和公共 API,那才是它的主场。