本文献给:
已掌握 JavaScript 基础,并熟悉 TypeScript 中 string、number、boolean 及类型注解的开发者。本文将系统讲解 TypeScript 中数组、元组与枚举的定义与使用,帮助你精准描述更复杂的数据结构。
你将学到:
- 数组的三种定义方式与类型推断
- 元组的固定长度、可选元素与剩余元素
- 数字枚举、字符串枚举及常量枚举的区别
- 枚举编译后的 JavaScript 产物分析
- 常见错误与最佳实践
目录
- 一、数组类型(Array)
-
- [1.1 基本定义](#1.1 基本定义)
- [1.2 数组的类型推断](#1.2 数组的类型推断)
- [1.3 只读数组](#1.3 只读数组)
- [1.4 多维数组](#1.4 多维数组)
- 二、元组(Tuple)
-
- [2.1 基本元组](#2.1 基本元组)
- [2.2 可选元素](#2.2 可选元素)
- [2.3 剩余元素(Rest Elements)](#2.3 剩余元素(Rest Elements))
- [2.4 只读元组](#2.4 只读元组)
- [2.5 元组与数组的区别](#2.5 元组与数组的区别)
- 三、枚举(Enum)
- 四、常见错误与注意事项
-
- [4.1 数组赋值类型不匹配](#4.1 数组赋值类型不匹配)
- [4.2 元组越界访问](#4.2 元组越界访问)
- [4.3 枚举的陷阱:数字枚举反向映射污染](#4.3 枚举的陷阱:数字枚举反向映射污染)
- [4.4 常量枚举与 `isolatedModules` 不兼容](#4.4 常量枚举与
isolatedModules不兼容) - [4.5 `as const` 与枚举的选择](#4.5
as const与枚举的选择)
- 五、综合示例
- 六、小结
一、数组类型(Array)
在 TypeScript 中,数组类型有两种等效的写法:类型 + 方括号 或 泛型 Array<类型>。
1.1 基本定义
typescript
// 方式一:类型 + []
let list1: number[] = [1, 2, 3];
let list2: string[] = ['a', 'b', 'c'];
// 方式二:泛型 Array<类型>
let list3: Array<number> = [1, 2, 3];
let list4: Array<string> = ['a', 'b', 'c'];
两种方式完全等价,推荐使用 类型[],更简洁易读。
1.2 数组的类型推断
初始化数组时,TypeScript 会根据元素类型推断数组类型:
typescript
let arr1 = [1, 2, 3]; // 推断为 number[]
let arr2 = ['hello', 'ts']; // 推断为 string[]
let arr3 = [1, 'hello']; // 推断为 (string | number)[]
let arr4 = []; // 推断为 any[](需谨慎)
如果数组初始为空,后续推入元素也会影响类型推断:
typescript
let arr = []; // any[]
arr.push(1); // 此时 arr 仍然是 any[]
arr.push('a'); // 仍然允许
// 更好的做法:显式注解
let arr2: number[] = [];
arr2.push(1); // OK
arr2.push('a'); // ❌
1.3 只读数组
有时我们希望数组创建后不可修改,可以使用 readonly 或 ReadonlyArray<T>。
typescript
// 方式一:readonly 修饰符
let roArr: readonly number[] = [1, 2, 3];
roArr[0] = 10; // ❌ 无法赋值
roArr.push(4); // ❌ 属性 push 不存在
// 方式二:ReadonlyArray<T> 泛型
let roArr2: ReadonlyArray<number> = [1, 2, 3];
// 方式三:使用 as const(更严格,变成字面量只读元组)
let roArr3 = [1, 2, 3] as const;
// roArr3 类型为 readonly [1, 2, 3]
提示 :
readonly number[]与ReadonlyArray<number>等价,但前者更简洁。
1.4 多维数组
typescript
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6]
];
let deep: string[][][] = [[['a']]]; // 三维数组
二、元组(Tuple)
元组允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
2.1 基本元组
typescript
let tuple: [string, number] = ['Alice', 25];
// 类型顺序必须匹配
let wrong: [string, number] = [25, 'Alice']; // ❌
访问元素时,TypeScript 会正确推断对应位置类型:
typescript
let name = tuple[0]; // string
let age = tuple[1]; // number
2.2 可选元素
在元组中可以使用 ? 标记可选元素,可选元素必须位于必选元素之后。
typescript
let tuple2: [string, number?] = ['Alice']; // OK
let tuple3: [string, number?] = ['Alice', 25]; // OK
let tuple4: [string, number?] = [25]; // ❌ 第一个必须是 string
2.3 剩余元素(Rest Elements)
使用 ... 定义不定长度的元组,剩余元素必须是数组类型。
typescript
// 至少一个 string,后面任意多个 number
let tuple5: [string, ...number[]] = ['Alice', 1, 2, 3];
// 开头任意多个 boolean,最后一个是 string
let tuple6: [...boolean[], string] = [true, false, 'end'];
// 混合使用:string, 可选 number, 任意多个 boolean
let tuple7: [string, number?, ...boolean[]] = ['start', true, false];
2.4 只读元组
typescript
let roTuple: readonly [string, number] = ['Alice', 25];
roTuple[0] = 'Bob'; // ❌ 只读属性
// 或者使用 as const
let roTuple2 = ['Alice', 25] as const;
// 类型为 readonly ["Alice", 25]
2.5 元组与数组的区别
| 特性 | 数组 number[] |
元组 [string, number] |
|---|---|---|
| 元素类型 | 全部相同 | 可以不同 |
| 长度 | 可变 | 固定(除剩余元素外) |
| 常见场景 | 列表、集合 | 固定结构(如坐标、键值对) |
三、枚举(Enum)
枚举是 TypeScript 对 JavaScript 的扩展,用于定义一组命名常量,使代码更易读。
3.1 数字枚举
数字枚举的成员默认从 0 开始自增。
typescript
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
let dir: Direction = Direction.Up;
console.log(dir); // 0
可以手动赋值:
typescript
enum Status {
Pending = 1,
Approved, // 2(自动递增)
Rejected // 3
}
// 部分手动,部分自动
enum Color {
Red = 1,
Green, // 2
Blue = 10,
Yellow // 11
}
数字枚举支持反向映射(通过值获取键名):
typescript
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction[0]); // "Up"
console.log(Direction.Up); // 0
3.2 字符串枚举
字符串枚举的每个成员必须用字符串字面量初始化,没有自增行为。
typescript
enum LogLevel {
Error = "ERROR",
Warn = "WARN",
Info = "INFO"
}
let level: LogLevel = LogLevel.Error;
console.log(level); // "ERROR"
字符串枚举不支持反向映射。
3.3 异构枚举(混合)
不推荐使用,除非有特殊需求。
typescript
enum Mixed {
No = 0,
Yes = "YES"
}
3.4 常量枚举(const enum)
常量枚举在编译时会被内联,不生成真实的对象代码,性能更好。
typescript
const enum Direction {
Up,
Down,
Left,
Right
}
let move = Direction.Up;
编译后:
javascript
// 编译结果
let move = 0 /* Direction.Up */;
常量枚举的限制 :不能使用反向映射,且只能在外部模块中访问(如果 preserveConstEnums 为 false,默认如此)。
3.5 枚举编译产物分析
普通数字枚举
typescript
// TypeScript
enum Color {
Red,
Green,
Blue
}
编译后的 JavaScript:
javascript
// JavaScript
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
生成的 Color 对象同时包含正向映射(Color.Red -> 0)和反向映射(Color[0] -> "Red")。
字符串枚举
typescript
enum LogLevel {
Error = "ERROR",
Warn = "WARN"
}
编译后:
javascript
var LogLevel;
(function (LogLevel) {
LogLevel["Error"] = "ERROR";
LogLevel["Warn"] = "WARN";
})(LogLevel || (LogLevel = {}));
没有反向映射。
常量枚举
常量枚举在编译后完全消失,所有引用被替换为字面量值。
typescript
const enum Size {
Small = 1,
Large = 2
}
let s = Size.Small;
编译后:
javascript
let s = 1; // 直接替换为值
3.6 枚举与字面量联合类型对比
现代 TypeScript 推荐使用字面量联合类型替代简单枚举,尤其是字符串枚举。
typescript
// 枚举方式
enum Direction {
Up = "UP",
Down = "DOWN"
}
// 字面量联合类型(更轻量,无运行时开销)
type Direction = "UP" | "DOWN";
let dir: Direction = "UP";
选择建议:
- 需要反向映射、自增行为或运行时枚举对象 → 使用数字枚举
- 仅需一组常量字符串,且不关心运行时 → 使用字面量联合类型
- 需要将枚举值作为独立类型使用,并且不希望产生额外代码 → 使用
const enum(但注意打包工具兼容性)
四、常见错误与注意事项
4.1 数组赋值类型不匹配
typescript
let nums: number[] = [1, 2, 3];
nums = ['a', 'b']; // ❌ string 不能赋给 number
4.2 元组越界访问
typescript
let tuple: [string, number] = ['Alice', 25];
let val = tuple[2]; // ❌ 长度为 2,索引 2 越界
4.3 枚举的陷阱:数字枚举反向映射污染
typescript
enum Status {
Active = 1,
Inactive
}
// 编译后,Status 对象额外包含反向映射键,可能干扰对象遍历
4.4 常量枚举与 isolatedModules 不兼容
在使用 ts-loader 或 babel 且开启 isolatedModules 时,常量枚举可能出错。此时需要改用普通枚举或禁用常量枚举。
4.5 as const 与枚举的选择
typescript
// as const 生成只读元组/对象,没有独立类型名
const colors = ['red', 'green', 'blue'] as const; // 类型为 readonly ["red", "green", "blue"]
// 需要重复使用类型别名时,更适合用枚举或字面量联合
type Color = 'red' | 'green' | 'blue';
五、综合示例
typescript
// 定义一个学生元组:[姓名, 年龄, 可选性别]
type Student = [string, number, 'male' | 'female'?];
// 定义一个班级类
class ClassRoom {
private students: Student[] = [];
addStudent(...student: Student) {
this.students.push(student);
}
getAllStudents(): Student[] {
return this.students;
}
// 枚举用于成绩等级
getGrade(score: number): Grade {
if (score >= 90) return Grade.A;
if (score >= 75) return Grade.B;
if (score >= 60) return Grade.C;
return Grade.D;
}
}
// 数字枚举
enum Grade {
A = 90,
B = 75,
C = 60,
D = 0
}
// 使用示例
const classroom = new ClassRoom();
classroom.addStudent('Alice', 20, 'female');
classroom.addStudent('Bob', 22); // 性别可选
console.log(classroom.getGrade(85)); // 输出 75(Grade.B)
// 遍历学生
for (const [name, age, gender] of classroom.getAllStudents()) {
console.log(`${name}, ${age}岁${gender ? `, 性别 ${gender}` : ''}`);
}
六、小结
| 类型 | 语法示例 | 特点 |
|---|---|---|
| 数组 | let arr: number[] = [1,2] |
同类型,长度可变 |
| 只读数组 | let ro: readonly number[] = [1] |
不可修改 |
| 元组 | let t: [string, number] = ['a',1] |
固定长度,类型可不同 |
| 可选元组 | let t: [string, number?] = ['a'] |
元素可选 |
| 剩余元组 | let t: [string, ...number[]] |
不定长尾部 |
| 数字枚举 | enum E { A, B } |
自动递增,支持反向映射 |
| 字符串枚举 | enum E { A = "a", B = "b" } |
无反向映射 |
| 常量枚举 | const enum E { A, B } |
编译时内联,无运行时对象 |
觉得文章有帮助?别忘了:
👍 点赞 👍 -- 给我一点鼓励
⭐ 收藏 ⭐ -- 方便以后查看
🔔 关注 🔔 -- 获取更新通知
标签: #TypeScript #数组 #元组 #枚举 #学习笔记 #前端开发