第4章 复合类型
本章定位:学习TypeScript中用于构建复杂数据结构的复合类型,包括数组、元组、对象类型、接口、联合类型、交叉类型和枚举。
4.1 数组类型与元组类型
Section titled “4.1 数组类型与元组类型”基础数组声明:
// 两种等价写法let numbers: number[] = [1, 2, 3, 4, 5];let names: Array<string> = ["Alice", "Bob", "Charlie"];
// 数组推导const mixed = [1, "hello", true]; // (number | string | boolean)[]Python对比:
# Python列表numbers = [1, 2, 3, 4, 5]names = ["Alice", "Bob", "Charlie"]
# 类型注解(仅供参考)from typing import Listnumbers: List[int] = [1, 2, 3, 4, 5]数组常用操作
Section titled “数组常用操作”访问与修改:
const fruits: string[] = ["apple", "banana", "cherry"];
// 访问元素fruits[0]; // "apple"fruits[10]; // undefined(越界不报错)
// 修改元素fruits[1] = "blueberry";
// 添加元素fruits.push("date"); // 末尾添加fruits.unshift("apricot"); // 开头添加fruits.splice(2, 0, "elderberry"); // 在索引2前插入
// 删除元素fruits.pop(); // 末尾删除fruits.shift(); // 开头删除fruits.splice(1, 1); // 删除索引1的元素Python对比:
fruits = ["apple", "banana", "cherry"]
# 访问元素fruits[0] # "apple"fruits[10] # IndexError
# 修改元素fruits[1] = "blueberry"
# 添加元素fruits.append("date")fruits.insert(0, "apricot")
# 删除元素fruits.pop()fruits.remove("banana")del fruits[1]数组方法与函数式编程
Section titled “数组方法与函数式编程”const numbers: number[] = [1, 2, 3, 4, 5];
// map - 转换每个元素const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter - 过滤元素const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce - 累积计算const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
// find - 查找第一个匹配const firstEven = numbers.find(n => n % 2 === 0); // 2
// some - 是否有匹配const hasNegative = numbers.some(n => n < 0); // false
// every - 是否全部匹配const allPositive = numbers.every(n => n > 0); // true
// sort - 排序(注意:原地修改)const sorted = [...numbers].sort((a, b) => a - b); // [1, 2, 3, 4, 5]
// reverse - 反转const reversed = [...numbers].reverse(); // [5, 4, 3, 2, 1]
// flatMap - 先map再flatconst flatResult = [1, 2, 3].flatMap(n => [n, n * 2]); // [1, 2, 2, 4, 3, 6]Python对比:
numbers = [1, 2, 3, 4, 5]
# map - 转换每个元素doubled = list(map(lambda n: n * 2, numbers)) # [2, 4, 6, 8, 10]
# filter - 过滤元素evens = list(filter(lambda n: n % 2 == 0, numbers)) # [2, 4]
# reduce - 累积计算from functools import reducetotal = reduce(lambda acc, n: acc + n, numbers, 0) # 15
# 列表推导式(更Pythonic)doubled = [n * 2 for n in numbers]evens = [n for n in numbers if n % 2 == 0]// 二维数组const matrix: number[][] = [ [1, 2, 3], [4, 5, 6], [7, 8, 9]];
matrix[0][1]; // 2
// 三维数组const tensor: number[][][] = [ [[1, 2], [3, 4]], [[5, 6], [7, 8]]];
// 创建特定大小的数组const zeros = Array(5).fill(0); // [0, 0, 0, 0, 0]const range = Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]Python对比:
# 二维数组matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix[0][1] # 2
# 创建特定大小的数组zeros = [[0] * 5 for _ in range(5)]range_list = list(range(5)) # [0, 1, 2, 3, 4]元组是固定长度、固定类型的数组:
// 基本元组let point: [number, number] = [10, 20];point[0] = 15;point[1] = 25;// point[2] = 30; // 编译错误!元组长度固定
// 可选元素(元组长度可变)let user: [string, number, boolean?];user = ["Alice", 30];user = ["Bob", 25, true];
// 剩余元素let list: [number, ...string[]];list = [1, "a", "b", "c"]; // 第一个是number,后面任意数量string
// 只读元组const pointReadonly: readonly [number, number] = [10, 20];// pointReadonly[0] = 20; // 编译错误!元组实际应用
Section titled “元组实际应用”// 函数返回多值function getUser(): [string, number, string] { return ["Alice", 30, "admin"];}
const [name, age, role] = getUser();
// CSV行处理type CSVRow = [string, string, number, Date];const rows: CSVRow[] = [ ["Alice", "Engineering", 30, new Date("2020-01-15")], ["Bob", "Sales", 25, new Date("2019-06-20")]];
// 解构赋值function processPoint([x, y]: [number, number]): number { return Math.sqrt(x * x + y * y);}processPoint([3, 4]); // 5Python对比:
# Python使用列表或namedtuple模拟元组from typing import Tuple
# 基本元组point: Tuple[int, int] = (10, 20)
# 函数返回多值def get_user() -> Tuple[str, int, str]: return ("Alice", 30, "admin")
name, age, role = get_user()
# CSV行处理Row = Tuple[str, str, int, str]rows: List[Row] = [ ("Alice", "Engineering", 30, "2020-01-15"), ("Bob", "Sales", 25, "2019-06-20")]4.2 对象类型与接口(interface)
Section titled “4.2 对象类型与接口(interface)”对象类型字面量
Section titled “对象类型字面量”// 基础对象类型const user: { name: string; age: number } = { name: "Alice", age: 30};
// 可选属性const user2: { name: string; age?: number } = { name: "Bob"};
// 只读属性const config: { readonly apiUrl: string; timeout: number } = { apiUrl: "https://api.example.com", timeout: 5000};// config.apiUrl = "https://other.com"; // 编译错误!
// 动态属性(索引签名)const dictionary: { [key: string]: string } = {};dictionary["hello"] = "world";dictionary["foo"] = "bar";interface 接口
Section titled “interface 接口”interface是定义对象结构的推荐方式:
// 基本接口interface User { name: string; age: number; email?: string; // 可选属性 readonly id: string; // 只读属性}
// 使用接口function greetUser(user: User): string { return `Hello, ${user.name}!`;}
const user: User = { name: "Alice", age: 30, id: "user-123" // 创建后不能修改};
greetUser(user); // OKgreetUser({ name: "Bob", age: 25, id: "user-456" }); // OKPython对比:
# Python使用类或dataclassfrom dataclasses import dataclass
@dataclassclass User: name: str age: int email: str | None = None
# 不可变dataclass@dataclass(frozen=True)class Config: api_url: str timeout: intinterface Animal { name: string; age: number;}
interface Dog extends Animal { breed: string;}
interface GuideDog extends Dog { certificationLevel: string;}
const guideDog: GuideDog = { name: "Buddy", age: 5, breed: "Golden Retriever", certificationLevel: "AAA"};interface Walkable { walk(): void;}
interface Runnable { run(): void;}
interface Amphibious extends Walkable, Runnable { swim(): void;}
class Frog implements Amphibious { walk(): void { console.log("Frog walking"); }
run(): void { console.log("Frog running"); }
swim(): void { console.log("Frog swimming"); }}接口合并(Declaration Merging)
Section titled “接口合并(Declaration Merging)”interface Window { title: string;}
interface Window { width: number; height: number;}
// 合并后的Window接口// { title: string; width: number; height: number }Python对比:Python的类可以多重继承,但不会自动合并。
接口 vs 类型别名
Section titled “接口 vs 类型别名”// interfaceinterface User { name: string; age: number;}
// type aliastype User = { name: string; age: number;};
// 都可以继承interface Animal { name: string;}
interface Dog extends Animal { breed: string;}
type Cat = { name: string; color: string;} & { age: number }; // 交叉类型
// interface更擅长:合并、类实现// type alias更擅长:联合类型、工具类型4.3 联合类型与交叉类型
Section titled “4.3 联合类型与交叉类型”联合类型(Union Types)
Section titled “联合类型(Union Types)”用|连接多个类型,表示”可以是其中之一”:
// 基本联合类型let value: string | number;value = "hello"; // OKvalue = 123; // OKvalue = true; // 编译错误!
// 联合类型的变量只能访问所有类型共有的成员function process(value: string | number) { // 两者都有toString() value.toString();
// 只有string有split() if (typeof value === "string") { value.split(","); // OK,类型收缩为string }
// 只有number有toFixed() if (typeof value === "number") { value.toFixed(2); // OK,类型收缩为number }}联合类型字面量
Section titled “联合类型字面量”// 字面量联合类型type Status = "pending" | "approved" | "rejected";
let currentStatus: Status;currentStatus = "pending"; // OKcurrentStatus = "done"; // 编译错误!
type Direction = "north" | "south" | "east" | "west";
function move(direction: Direction): void { switch (direction) { case "north": console.log("Moving north"); break; case "south": console.log("Moving south"); break; case "east": console.log("Moving east"); break; case "west": console.log("Moving west"); break; }}可辨识联合(Tagged Union)
Section titled “可辨识联合(Tagged Union)”interface SuccessResponse { kind: "success"; data: string;}
interface ErrorResponse { kind: "error"; message: string;}
interface LoadingResponse { kind: "loading";}
type APIResponse = SuccessResponse | ErrorResponse | LoadingResponse;
function handleResponse(response: APIResponse): string { switch (response.kind) { case "success": return `Data: ${response.data}`; case "error": return `Error: ${response.message}`; case "loading": return "Loading..."; }}Python对比:
from typing import Union, Literalfrom dataclasses import dataclass
@dataclassclass SuccessResponse: kind: Literal["success"] data: str
@dataclassclass ErrorResponse: kind: Literal["error"] message: str
# Python没有内置的可辨识联合,需要手动检查kinddef handle_response(response): if response.kind == "success": return f"Data: {response.data}" elif response.kind == "error": return f"Error: {response.message}"交叉类型(Intersection Types)
Section titled “交叉类型(Intersection Types)”用&连接多个类型,表示”同时满足所有类型”:
interface Printable { print(): void;}
interface Saveable { save(): void;}
interface Document extends Printable, Saveable { content: string;}
// 或者用交叉类型type PrintableDocument = Printable & { content: string };
// 组合多个功能type Input = { value: string };type Validated = { isValid: boolean };type ValidatedInput = Input & Validated;
const input: ValidatedInput = { value: "hello", isValid: true};联合与交叉的组合
Section titled “联合与交叉的组合”type Dog = { breed: string };type Cat = { color: string };
// 联合类型:可以是Dog或Cattype Pet = Dog | Cat;
// 交叉类型:同时是Dog和Cat// 不可能存在,除非有继承关系type DogCat = Dog & Cat;4.4 类型别名(type alias)
Section titled “4.4 类型别名(type alias)”// 基础类型别名type ID = string | number;
// 对象类型别名type Point = { x: number; y: number;};
// 函数类型别名type Callback = (error: Error | null, result?: string) => void;
// 元组类型别名type Pair<T> = [T, T];
// 工具类型别名type StringList = string[];type NumberDictionary = { [key: string]: number };泛型类型别名
Section titled “泛型类型别名”// 带类型参数type Container<T> = { value: T; getValue(): T;};
type Nullable<T> = T | null;type NonNullable<T> = T extends null | undefined ? never : T;
type Optional<T> = { [P in keyof T]?: T[P];};
// 使用const container: Container<string> = { value: "hello", getValue: () => "hello" };const nullableName: Nullable<string> = null;const validName: NonNullable<string | null> = "Alice"; // string类型别名与接口的选择
Section titled “类型别名与接口的选择”| 场景 | 推荐 | 原因 |
|---|---|---|
| 定义对象结构 | interface | 更清晰,可合并 |
| 定义函数类型 | type | 简洁 |
| 需要泛型 | 都可以 | 语法相似 |
| 需要继承 | interface | extends更直观 |
| 定义联合类型 | type | |
| 定义交叉类型 | type | &操作符自然 |
4.5 枚举类型(enum)
Section titled “4.5 枚举类型(enum)”enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3}
enum Status { Pending = 1, Approved, Rejected}
console.log(Direction.Up); // 0console.log(Direction[0]); // "Up"(反向映射)console.log(Status.Approved); // 2Python对比:
from enum import Enum
class Direction(Enum): Up = 0 Down = 1 Left = 2 Right = 3
print(Direction.Up) # Direction.Upprint(Direction.Up.value) # 0enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT"}
console.log(Direction.Up); // "UP"console.log(Direction["UP"]); // Direction.Up异构枚举(不推荐)
Section titled “异构枚举(不推荐)”// 混合数字和字符串(不推荐)enum Mixed { No = 0, Yes = "YES"}const枚举
Section titled “const枚举”// const枚举:编译时内联,运行时无访问器const enum HttpStatus { OK = 200, NotFound = 404, ServerError = 500}
// 使用时被内联const code = HttpStatus.OK; // 直接替换为200enum LogLevel { Debug, Info, Warning, Error}
function log(level: LogLevel, message: string): void { if (level >= LogLevel.Warning) { console.log(`[${LogLevel[level]}] ${message}`); }}
log(LogLevel.Info, "Starting process");log(LogLevel.Warning, "Memory usage high");log(LogLevel.Error, "Connection failed");4.6 null与undefined:可空类型处理
Section titled “4.6 null与undefined:可空类型处理”TypeScript区分null和undefined:
let value1: null = null;let value2: undefined = undefined;
let name: string | null = null;let age: number | undefined = undefined;严格null检查
Section titled “严格null检查”function greet(name: string | null): string { if (name === null) { return "Hello, stranger!"; } return `Hello, ${name}!`;}可选链(Optional Chaining)
Section titled “可选链(Optional Chaining)”安全访问深层属性:
interface Company { name: string; address?: { street: string; city: string; };}
const company: Company = { name: "TechCorp"};
// 传统写法const street = company.address ? company.address.street : undefined;
// 可选链写法const street2 = company.address?.street; // undefinedconst street3 = company.address?.city; // undefined
// 深层可选链const zip = company.address?.zipCode?.value; // undefined,不报错空值合并(Nullish Coalescing)
Section titled “空值合并(Nullish Coalescing)”// ?? 操作符:当左侧为null/undefined时使用右侧值const name: string | null = null;const displayName = name ?? "Anonymous"; // "Anonymous"
const count: number | undefined = undefined;const displayCount = count ?? 0; // 0
// 对比 || 操作符const empty = "";const result1 = empty ?? "default"; // ""(空字符串不是null/undefined)const result2 = empty || "default"; // "default"(空字符串是falsy)可选属性与必填属性
Section titled “可选属性与必填属性”interface User { name: string; email?: string; // 可选 age: number | undefined; // 可以是undefined,但不是可选 readonly id: string; // 只读}
// 初始化时const user: User = { name: "Alice", id: "user-123" // email和age可以不提供};4.7 链式调用与可选链
Section titled “4.7 链式调用与可选链”什么是链式调用
Section titled “什么是链式调用”当一个函数的返回值是另一个对象时,继续调用该对象的方法:
// 不使用可选链const street = user && user.company && user.company.address && user.company.address.street;
// 使用可选链const street2 = user?.company?.address?.street;方法的可选链
Section titled “方法的可选链”interface Manager { greet(): string;}
interface Employee { manager?: Manager;}
const employee: Employee = {};
// 传统写法const greeting = employee.manager ? employee.manager.greet() : undefined;
// 可选链调用方法const greeting2 = employee.manager?.greet();
// 如果greet可能不存在interface Employee2 { manager?: { greet?: () => string; };}
const greeting3 = employee.manager?.greet?.();可选链与数组
Section titled “可选链与数组”interface Config { servers?: Array<{ url: string; port: number }>;}
const config: Config = {};
// 传统写法const firstServer = config.servers && config.servers[0];
// 可选链const firstServer2 = config.servers?.[0];
// 可选链调用数组方法const serverCount = config.servers?.length ?? 0;4.8 空值合并(nullish coalescing)
Section titled “4.8 空值合并(nullish coalescing)”?? vs ||
Section titled “?? vs ||”// ?? 只在 null/undefined 时使用默认值null ?? "default"; // "default"undefined ?? "default"; // "default""" ?? "default"; // ""(空字符串不触发)0 ?? "default"; // 0false ?? "default"; // false
// || 在所有falsy值时使用默认值null || "default"; // "default"undefined || "default"; // "default""" || "default"; // "default"0 || "default"; // "default"false || "default"; // "default"实际应用场景
Section titled “实际应用场景”// 配置默认值interface Config { timeout?: number; retries?: number; endpoint?: string;}
const config: Config = { timeout: 0, // 显式设置为0 retries: 3};
const timeout = config.timeout ?? 3000; // 0(正确保留)const retries = config.retries ?? 3; // 3
// 优先级:显式0/false > 默认值// || 会错误地覆盖const timeoutOr = config.timeout || 3000; // 3000(错误!)链式空值合并
Section titled “链式空值合并”// 链式使用function getPort(config: { server?: { port?: number };}): number { return config.server?.port ?? 8080;}
getPort({}); // 8080getPort({ server: {} }); // 8080getPort({ server: { port: 3000 } }); // 30004.9 本章小结
Section titled “4.9 本章小结”本章学习了TypeScript的复合类型:
-
数组类型:
number[]或Array<number>声明- 丰富的内置方法(map、filter、reduce等)
- 支持多维数组
-
元组类型:
- 固定长度、固定类型的数组
- 支持可选元素和剩余元素
- 解构赋值很方便
-
对象类型与接口:
- interface定义对象结构
- 支持继承、合并
- 可选属性、只读属性
-
联合类型与交叉类型:
- 联合类型:
|表示或 - 交叉类型:
&表示且
- 联合类型:
-
类型别名:
- 给复杂类型起别名
- 支持泛型
-
枚举类型:
- 数字枚举和字符串枚举
- const枚举编译时内联
-
null/undefined处理:
- 可选链
?.安全访问 - 空值合并
??提供默认值
- 可选链
练习4.1:数组操作
Section titled “练习4.1:数组操作”创建numbers数组,实现:
- 过滤出偶数
- 计算平均值
- 找出最大值
练习4.2:接口定义
Section titled “练习4.2:接口定义”定义Product接口,包含:
id: string(只读)name: stringprice: numbertags?: string[]calculateDiscount(rate: number): number方法
练习4.3:联合类型
Section titled “练习4.3:联合类型”创建Result类型,可以是{ success: true; data: User }或{ success: false; error: string }。编写处理函数。
练习4.4:可选链与空值合并
Section titled “练习4.4:可选链与空值合并”给Company结构体添加多层可选属性,练习安全访问和默认值设置。
练习4.5:枚举应用
Section titled “练习4.5:枚举应用”创建一个表示HTTP状态码的枚举,编写根据状态码返回描述的函数。
下一章我们将学习TypeScript的面向对象编程,包括类、继承、访问修饰符、抽象类等概念,与Python的类系统进行对比。