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]变联合类型——三步让数组的值和类型永远同步,只维护一个地方。

浙公网安备 33010602011771号