TypeScript枚举与元组详解
本文是TypeScript系列第九篇,将详细介绍枚举和元组这两种特殊的类型。它们提供了更精确的方式来组织数据,让代码在表达复杂数据结构时更加清晰和类型安全。
枚举(Enum)是TypeScript提供的一种特性,用于定义一组相关的命名常量。使用枚举可以替代魔法数字和字符串,让代码更加可读和可维护。
枚举的核心价值:
数字枚举是最常见的枚举类型,它的成员值默认从0开始自动递增。
基本定义:
// 基本的数字枚举
enum Direction {
Up, // 值为 0
Down, // 值为 1
Left, // 值为 2
Right // 值为 3
}
// 使用枚举
let move: Direction = Direction.Up;
console.log(move); // 输出: 0
手动设置初始值:
// 手动设置枚举值
enum StatusCode {
Success = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404
}
// 使用
const response = StatusCode.Success; // 200
递增特性:
// 从特定值开始递增
enum LogLevel {
Error = 1, // 1
Warn, // 2
Info, // 3
Debug // 4
}
字符串枚举的每个成员都必须用字符串字面量初始化。字符串枚举没有自增长行为,但提供了更好的序列化和调试体验。
字符串枚举定义:
// 字符串枚举
enum MessageType {
Success = "SUCCESS",
Error = "ERROR",
Warning = "WARNING"
}
// 使用
const message = MessageType.Success; // "SUCCESS"
字符串枚举的优势:
常量枚举使用const关键字定义,它在编译时会被完全删除,只保留使用到的值,从而提供更好的性能。
常量枚举定义:
// 常量枚举
const enum Size {
Small = "S",
Medium = "M",
Large = "L"
}
// 使用
const selectedSize = Size.Medium;
编译后的结果:
// 编译后的JavaScript代码
const selectedSize = "M"; // 枚举被完全内联
常量枚举的使用场景:
异构枚举混合了数字和字符串成员,虽然TypeScript支持这种用法,但在实际开发中应该尽量避免,因为它会增加复杂性。
异构枚举示例:
// 不推荐的异构枚举
enum MixedEnum {
No = 0,
Yes = "YES"
}
元组(Tuple)类型表示一个已知元素数量和类型的数组。与普通数组不同,元组的每个位置都有特定的类型。
元组的核心价值:
基本元组定义:
// 简单的二元组
let coordinates: [number, number] = [10, 20];
// 混合类型的元组
let userInfo: [string, number, boolean] = ["Alice", 25, true];
元组的类型安全:
//错误:类型不匹配
let point: [number, number] = [10, "20"]; // 第二个元素应该是数字
// 错误:长度不匹配
let data: [string, number] = ["hello"]; // 缺少第二个元素
元组支持可选元素,使用问号(?)标记。可选元素必须在必选元素之后。
可选元素语法:
// 包含可选元素的元组
type OptionalTuple = [string, number?];
// 有效的使用方式
let data1: OptionalTuple = ["hello"]; // 第二个元素可选
let data2: OptionalTuple = ["hello", 42]; // 提供第二个元素
元组可以使用剩余元素语法来表示"固定开头+可变结尾"的模式。
剩余元素语法:
// 固定开头,剩余元素类型相同
type StringNumberBooleans = [string, number, ...boolean[]];
let data: StringNumberBooleans = ["hello", 1, true, false, true];
实际应用场景:
// 函数参数:固定参数 + 可变参数
function logMessage(level: string, timestamp: number, ...messages: string[]) {
console.log(`[${level}] ${new Date(timestamp).toISOString()}:`, ...messages);
}
// 使用
logMessage("ERROR", Date.now(), "系统错误", "请检查日志");
只读元组使用readonly关键字,确保元组创建后不能被修改。
只读元组语法:
// 只读元组
const point: readonly [number, number] = [10, 20];
// 错误:无法修改只读元组
point[0] = 15;
只读元组的简写语法:
// 使用as const创建只读元组
const point = [10, 20] as const; // 类型为 readonly [10, 20]
场景1:状态管理
// 应用状态枚举
enum AppState {
Idle = "IDLE",
Loading = "LOADING",
Success = "SUCCESS",
Error = "ERROR"
}
let currentState: AppState = AppState.Idle;
function handleStateChange(newState: AppState) {
currentState = newState;
// 处理状态变化
}
场景2:配置选项
// 主题配置枚举
enum Theme {
Light = "light",
Dark = "dark",
Auto = "auto"
}
// 语言枚举
enum Language {
Chinese = "zh-CN",
English = "en-US",
Japanese = "ja-JP"
}
const settings = {
theme: Theme.Dark,
language: Language.Chinese
};
场景1:React Hooks返回值
// useState返回的是元组类型
const [count, setCount] = useState(0);
// 类型为: [number, React.Dispatch<React.SetStateAction<number>>]
场景2:函数返回多个值
// 返回成功状态和数据的元组
function fetchData(): [boolean, string | null] {
try {
const data = "获取的数据";
return [true, data];
} catch {
return [false, null];
}
}
// 使用
const [success, data] = fetchData();
if (success) {
console.log(data);
}
场景3:特定格式的数据
枚举在以下场景中更合适:
常量对象在以下场景中更合适:
常量对象示例:
// 使用常量对象作为枚举的替代
const AppState = {
Idle: "IDLE",
Loading: "LOADING",
Success: "SUCCESS",
Error: "ERROR"
} as const;
// 获取类型
type AppState = typeof AppState[keyof typeof AppState];
枚举的数值比较:
enum Color {
Red = 1,
Green = 2
}
// 可能不安全的比较
if (someValue === Color.Red) { ... }
// 更安全的做法:先验证值
if (Object.values(Color).includes(someValue)) {
// 安全的比较
}
元组的越界访问:
const tuple: [string, number] = ["hello", 42];
// TypeScript允许但可能不安全的访问
const third = tuple[2]; // undefined
// 安全的做法:检查长度
if (tuple.length > 2) {
const third = tuple[2];
}