TypeScript - typeof 搭配 as const 技巧总结

这是一种 TypeScript 的高级类型技巧,用于从值推导出类型,实现类型和值的完美同步。

基本语法

/**
  常量数组声明 
  声明了一个名为 eventNames 的常量,其值是一个字符串数组。
  as const 这是 TypeScript 的 字面量常量断言
  没有 as const 时,TypeScript 会将数组的类型推断为 string[],这意味着数组的每个元素都被视为一个普通的、任意的字符串
  加上 as const 后,它的作用非常强大,它会告诉 TypeScript 将这个数组及其所有元素视为 不可变的字面量类型,具体来说,eventNames 的类型不再是 string[],而是被精确地推断为 readonly ["API:UN_AUTHORIZED", "API:INVAALID", "API:NETWORK_ERROR", "API:SESSION_EXPIRRED"], 这个类型包含了每个元素的确切值,并且数组本身是只读的
  为什么要这么写?目的是为了锁定数组的值,使其类型不是宽泛的 string,而是具体的字面量(如 "API:UN_AUTHORIZED")。
  这为下一行提取类型提供了坚实的基础,是实现类型安全的关键一步。
*/
const eventNames = ["API:UN_AUTHORIZED", "API:INVAALID", "API:NETWORK_ERROR", "API:SESSION_EXPIRRED"] as const;
/**
  类型提取
  这行代码利用第一行 as const 的结果,创建了一个新的类型。
  typeof eventNames: 这不是 JavaScript 的 typeof 运行时操作,而是 TypeScript 的类型查询操作符。
  它用于获取变量 eventNames 的类型,因为第一行有 as const,所以 typeof eventNames 的结果就是我们上面提到的精确类型:readonly ["API:UN_AUTHORIZED", "API:INVAALID", "API:NETWORK_ERROR", "API:SESSION_EXPIRRED"]
  [number] (索引访问类型):在类型上下文中,使用 [number] 可以获取数组所有元素的类型,可以理解为:“给我这个数组在通过数字索引访问时(arr[0], arr[1]...)所有可能返回值的类型。”因为数组的每个索引都是 number 类型。
  整体 typeof eventNames[number]:所以,这行代码的意思是:获取 eventNames 数组中所有元素字面量类型的联合类型。
  最终,EventName 类型被定义为:type EventName = "API:UN_AUTHORIZED" | "API:INVAALID" | "API:NETWORK_ERROR" | "API:SESSION_EXPIRRED"
  
*/
type EventName = typeof eventNames[number];
// EventName = "API:UN_AUTHORIZED" | "API:INVAALID" | "API:NETWORK_ERROR" | API:SESSION_EXPIRRED

三个关键组成部分

1. as const - 常量断言

// 没有 as const: string[]
const names1 = ["Alice", "Bob"];

// 有 as const: readonly ["Alice", "Bob"]
const names2 = ["Alice", "Bob"] as const;

2. typeof - 类型查询

// 获取值的类型
const arr = [1, 2, 3] as const;
type ArrType = typeof arr; // readonly [1, 2, 3]

3. [number] - 索引访问类型

const values = ["A", "B", "C"] as const;
type ValueType = typeof values[number]; // "A" | "B" | "C"

优势特点

// 只在这里定义一次
const eventNames = ["CREATE", "UPDATE", "DELETE"] as const;
type EventName = typeof eventNames[number];

// 类型自动同步:EventName = "CREATE" | "UPDATE" | "DELETE"  

自动同步

// 添加新值
const statuses = ["PENDING", "SUCCESS", "FAILED", "CANCELED"] as const;
type Status = typeof statuses[number];
// Status 自动变为: "PENDING" | "SUCCESS" | "FAILED" | "CANCELED"

运行时可用

// 可以在运行时使用数组
statuses.forEach(status => {
    console.log(`处理状态: ${status}`);
});

// 验证函数
function isValidStatus(status: string): status is Status {
    return statuses.includes(status as Status);
}

适用场景

1. 固定值集合

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

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

2. 配置选项

const sortOptions = ["name", "date", "price"] as const;
type SortOption = typeof sortOptions[number];

3. API 端点

const apiEndpoints = ["/users", "/posts", "/comments"] as const;
type ApiEndpoint = typeof apiEndpoints[number];

对比其他方案

方案 类型安全 运行时可用 维护性
typeof values[number]
直接联合类型
枚举 ⚠️

最佳实践

推荐使用

// 定义常量数组
const eventNames = ["API:UN_AUTHORIZED", "API:INVAALID", "API:NETWORK_ERROR", "API:SESSION_EXPIRRED"] as const;
type EventName = typeof eventNames[number];
/**
 * 监听事件
 * @param eventNme 事件名称
 * @param listener 
 */
on(eventName: EventName, listener: Function)  {
    if(eventNames.includes(eventName)) {
        // 运行时安全
        this.listeners[eventName].add(listener);
    }
}

避免使用

// 不需要运行时验证的简单场景
type SimpleStatus = "active" | "inactive";

常见错误

忘记 as const

const colors = ["red", "blue"]; // string[]
type Color = typeof colors[number]; // string

错误的使用场景

// 不适合动态生成的数据
const dynamicValues = fetchDataFromAPI(); // 运行时数据
type DynamicType = typeof dynamicValues[number]; // 可能不是预期的类型

总结

使用时机:当你需要一组固定的值,并且希望在编译时和运行时都能使用这些值。

核心价值:保持类型定义和实际值的完全同步,避免重复和维护负担。

记住口诀:as const 定值,typeof 取型,[number] 得联合,类型值永同步!

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