TypeScript - typeof 搭配 as const 技巧总结

TypeScript:从数组值推导联合类型

只写一次,类型和值永远同步。


问题:类型和值重复维护

// ❌ 定义了两次,改一个忘了改另一个,容易出错
const eventNames = ["UNAUTHORIZED", "NETWORK_ERROR", "TIMEOUT"]

type EventName = "UNAUTHORIZED" | "NETWORK_ERROR" | "TIMEOUT"

新增一个值,要改两个地方。


解决方案:三步一体

// 只在这里定义一次
const eventNames = ["UNAUTHORIZED", "NETWORK_ERROR", "TIMEOUT"] as const

// 类型自动从值推导出来
type EventName = typeof eventNames[number]
// EventName = "UNAUTHORIZED" | "NETWORK_ERROR" | "TIMEOUT"

新增值只改第一行,类型自动同步。


三个关键语法

1. as const — 锁定为字面量类型

// 没有 as const → 宽泛的 string[]
const names = ["Alice", "Bob"]
// 类型:string[]

// 有 as const → 精确的字面量类型
const names = ["Alice", "Bob"] as const
// 类型:readonly ["Alice", "Bob"]

as const 告诉 TypeScript:这个数组的值是固定的,每个元素不是泛泛的 string,而是确切的 "Alice""Bob"

2. typeof — 获取变量的类型

const arr = [1, 2, 3] as const

type ArrType = typeof arr
// 类型:readonly [1, 2, 3]

这里的 typeof 是 TypeScript 的类型查询,不是 JS 运行时的 typeof

3. [number] — 提取数组所有元素的联合类型

const values = ["A", "B", "C"] as const

type ValueType = typeof values[number]
// 类型:"A" | "B" | "C"

[number] 的意思是:给我这个数组通过数字索引(arr[0]arr[1]...)访问时所有可能值的类型,即所有元素组成的联合类型。


完整例子

const eventNames = [
  "API:UNAUTHORIZED",
  "API:NETWORK_ERROR",
  "API:TIMEOUT"
] as const

type EventName = typeof eventNames[number]

// 函数参数类型安全
function on(eventName: EventName, listener: Function) {
  // 运行时也可以用数组做校验
  if (eventNames.includes(eventName)) {
    console.log(`监听事件: ${eventName}`)
  }
}

on("API:UNAUTHORIZED", () => {})   // ✅
on("API:NOT_EXIST", () => {})      // ❌ 编译报错

运行时也能用

const statuses = ["PENDING", "SUCCESS", "FAILED"] as const
type Status = typeof statuses[number]

// 运行时遍历
statuses.forEach(s => console.log(s))

// 运行时验证
function isValidStatus(s: string): s is Status {
  return (statuses as readonly string[]).includes(s)
}

和其他方案对比

方案 类型安全 运行时可用 只维护一处
as const + typeof[number]
直接写联合类型 "A" | "B"
枚举 enum

和枚举的区别

  • as const 是纯 JS 值 + TS 类型,编译后数组还在
  • enum 编译后会生成额外的 JS 对象,有运行时开销
  • as const 更轻量,现代 TS 项目更推荐

进阶:泛型工厂函数

当数组需要被多处复用但保持类型推导时:

function createEventList<T extends string>(...events: T[]) {
  return events as unknown as readonly T[]
}

const events = createEventList("LOGIN", "LOGOUT", "ERROR")
type Event = typeof events[number]  // "LOGIN" | "LOGOUT" | "ERROR"

进阶:配合 satisfies(TS 4.9+)

既要约束值必须是某个超集的一部分,又要推导出精确的字面量:

type AllowedRole = "admin" | "user" | "guest"

const roles = ["admin", "user"] as const satisfies readonly AllowedRole[]
type Role = typeof roles[number]  // "admin" | "user"(精确的,不是 AllowedRole)

适用场景

// 主题
const themes = ["light", "dark", "system"] as const
type Theme = typeof themes[number]

// 用户角色
const roles = ["admin", "user", "guest"] as const
type Role = typeof roles[number]

// 排序选项
const sortBy = ["name", "date", "price"] as const
type SortBy = typeof sortBy[number]

不适合的场景

  • 动态数据:运行时才能确定的值,编译期无法推导
  • 简单二元场景:只有两个值,直接写联合类型更简洁,如 type Direction = "left" | "right"
  • 外部可配置数据:值可能通过配置文件变更,as const 需要重新编译

一句话总结

as const 锁定字面量,typeof 提取类型,[number] 变联合类型——三步让数组的值和类型永远同步,只维护一个地方。

posted @ 2025-09-20 22:45  HuangBingQuan  阅读(66)  评论(0)    收藏  举报