← 返回目录

第 2.5 章:数据结构

用正确的类型标注你的数据容器

🆚 与 Python 的对比:Python 的 list 是动态类型,可以混合类型;TS 的 Array 默认要求元素同类型。Python 的 dict 对应 TS 的 Record 或 Map。Python 的 tuple 是不可变的,TS 的 Tuple 侧重固定长度和类型。

1. Array

两种等价的数组类型标注方式,以及常用的类型安全数组方法:

// 两种写法完全等价
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ["a", "b", "c"];

// map —— 返回新数组,类型自动推断
const doubled: number[] = nums.map(n => n * 2);

// filter —— 可以用类型守卫缩窄类型
const mixed: (string | number)[] = [1, "a", 2, "b"];
const onlyStrings: string[] = mixed.filter(
    (x): x is string => typeof x === "string"
);

// reduce —— 需要提供初始值的类型
const sum: number = nums.reduce((acc, cur) => acc + cur, 0);

// find / some / every
const found: number | undefined = nums.find(n => n > 2);
const hasNeg: boolean = nums.some(n => n < 0);
const allPos: boolean = nums.every(n => n > 0);

// 只读数组
const frozen: readonly number[] = [1, 2, 3];
// frozen.push(4); // ✗ 编译错误

2. Tuple(元组)

元组是固定长度、每个位置有明确类型的数组。适合函数返回多个值的场景:

// 基本元组
const point: [number, number] = [10, 20];
const entry: [string, number] = ["age", 30];

// 带标签的元组——提高可读性
type Range = [start: number, end: number];
const r: Range = [0, 100];

// 可选元素
type OptionalTuple = [string, number?];
const t1: OptionalTuple = ["hello"];
const t2: OptionalTuple = ["hello", 42];

// Rest 元素
type StringAndNumbers = [string, ...number[]];
const sn: StringAndNumbers = ["sum", 1, 2, 3, 4];

// 实际用途:函数返回多个值
function useState<T>(initial: T): [T, (v: T) => void] {
    let value = initial;
    const setter = (v: T) => { value = v; };
    return [value, setter];
}
const [count, setCount] = useState(0);

解构元组时变量名任意,类型由位置决定。这与 Python 的 namedtuple 不同,更像 Go 的多返回值。

3. Enum(枚举)

// 数字枚举——默认从 0 开始自增
enum Direction {
    Up,    // 0
    Down,  // 1
    Left,  // 2
    Right  // 3
}

// 字符串枚举——每个成员必须显式赋值
enum Status {
    Active = "ACTIVE",
    Inactive = "INACTIVE",
    Pending = "PENDING"
}

// const enum——编译时内联,不生成运行时对象
const enum Color {
    Red = "#ff0000",
    Green = "#00ff00",
    Blue = "#0000ff"
}
const c = Color.Red; // 编译后直接替换为 "#ff0000"

// 数字枚举支持反向映射
console.log(Direction[0]); // "Up"
console.log(Direction.Up); // 0

枚举 vs 联合类型:大多数场景推荐用 type Status = "active" | "inactive" 联合类型替代枚举——更轻量且无运行时开销。枚举在需要反向映射或遍历成员时更有优势。

4. Map 与 Set

// Map<K, V> —— 有序键值对集合
const userMap = new Map<number, string>();
userMap.set(1, "Alice");
userMap.set(2, "Bob");

const name = userMap.get(1);     // string | undefined
const exists = userMap.has(3);   // false
userMap.delete(2);
console.log(userMap.size);       // 1

// 遍历 Map
for (const [id, name] of userMap) {
    console.log(`${id}: ${name}`);
}

// Set<T> —— 值唯一的集合
const tags = new Set<string>();
tags.add("typescript");
tags.add("javascript");
tags.add("typescript"); // 不会重复添加
console.log(tags.size); // 2

// WeakMap / WeakSet —— 键为弱引用,不阻止垃圾回收
const cache = new WeakMap<object, string>();
let obj = { id: 1 };
cache.set(obj, "cached");
// obj = null 后,cache 中的条目可被 GC 回收

5. Record 类型

Record<K, V> 用于创建键值对类型,类似 Python 的 Dict[str, T]

// Record 作为字典
type ScoreBoard = Record<string, number>;
const scores: ScoreBoard = {
    alice: 95,
    bob: 87,
    charlie: 92
};

// 限定键的范围
type Role = "admin" | "editor" | "viewer";
type Permissions = Record<Role, string[]>;
const perms: Permissions = {
    admin: ["read", "write", "delete"],
    editor: ["read", "write"],
    viewer: ["read"]
};

// Record vs Map:
// Record 编译为普通对象,键只能是 string | number | symbol
// Map 键可以是任意类型,有更好的增删性能和迭代顺序保证

6. 解构与展开

// 对象解构 + 类型注解
const user = { name: "Alice", age: 30, email: "a@b.com" };
const { name, age }: { name: string; age: number } = user;

// 重命名 + 默认值
const { name: userName, role = "user" } = { name: "Bob" };

// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first: number, second: number, rest: number[]

// 展开运算符——合并对象
const defaults = { theme: "light", lang: "zh" };
const custom = { theme: "dark" };
const config = { ...defaults, ...custom };
// { theme: "dark", lang: "zh" }

// 展开运算符——合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]

// Rest 参数解构
function printInfo({ name, ...rest }: {
    name: string;
    age: number;
    email: string;
}) {
    console.log(name, rest); // rest: { age: number; email: string }
}

📝 本章要点