Skip to content

第4章 复合类型

本章定位:学习TypeScript中用于构建复杂数据结构的复合类型,包括数组、元组、对象类型、接口、联合类型、交叉类型和枚举。


基础数组声明:

// 两种等价写法
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 List
numbers: List[int] = [1, 2, 3, 4, 5]

访问与修改:

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]
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再flat
const 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 reduce
total = 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; // 编译错误!
// 函数返回多值
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]); // 5

Python对比:

# 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")
]

// 基础对象类型
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是定义对象结构的推荐方式:

// 基本接口
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); // OK
greetUser({ name: "Bob", age: 25, id: "user-456" }); // OK

Python对比:

# Python使用类或dataclass
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str | None = None
# 不可变dataclass
@dataclass(frozen=True)
class Config:
api_url: str
timeout: int
interface 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");
}
}
interface Window {
title: string;
}
interface Window {
width: number;
height: number;
}
// 合并后的Window接口
// { title: string; width: number; height: number }

Python对比:Python的类可以多重继承,但不会自动合并。

// interface
interface User {
name: string;
age: number;
}
// type alias
type 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更擅长:联合类型、工具类型

用|连接多个类型,表示”可以是其中之一”:

// 基本联合类型
let value: string | number;
value = "hello"; // OK
value = 123; // OK
value = 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
}
}
// 字面量联合类型
type Status = "pending" | "approved" | "rejected";
let currentStatus: Status;
currentStatus = "pending"; // OK
currentStatus = "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;
}
}
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, Literal
from dataclasses import dataclass
@dataclass
class SuccessResponse:
kind: Literal["success"]
data: str
@dataclass
class ErrorResponse:
kind: Literal["error"]
message: str
# Python没有内置的可辨识联合,需要手动检查kind
def handle_response(response):
if response.kind == "success":
return f"Data: {response.data}"
elif response.kind == "error":
return f"Error: {response.message}"

用&连接多个类型,表示”同时满足所有类型”:

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
};
type Dog = { breed: string };
type Cat = { color: string };
// 联合类型:可以是Dog或Cat
type Pet = Dog | Cat;
// 交叉类型:同时是Dog和Cat
// 不可能存在,除非有继承关系
type DogCat = Dog & Cat;

// 基础类型别名
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 };
// 带类型参数
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
场景推荐原因
定义对象结构interface更清晰,可合并
定义函数类型type简洁
需要泛型都可以语法相似
需要继承interfaceextends更直观
定义联合类型type
定义交叉类型type&操作符自然

enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
enum Status {
Pending = 1,
Approved,
Rejected
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up"(反向映射)
console.log(Status.Approved); // 2

Python对比:

from enum import Enum
class Direction(Enum):
Up = 0
Down = 1
Left = 2
Right = 3
print(Direction.Up) # Direction.Up
print(Direction.Up.value) # 0
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
console.log(Direction.Up); // "UP"
console.log(Direction["UP"]); // Direction.Up
// 混合数字和字符串(不推荐)
enum Mixed {
No = 0,
Yes = "YES"
}
// const枚举:编译时内联,运行时无访问器
const enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
// 使用时被内联
const code = HttpStatus.OK; // 直接替换为200
enum 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");

TypeScript区分null和undefined:

let value1: null = null;
let value2: undefined = undefined;
let name: string | null = null;
let age: number | undefined = undefined;
function greet(name: string | null): string {
if (name === null) {
return "Hello, stranger!";
}
return `Hello, ${name}!`;
}

安全访问深层属性:

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; // undefined
const street3 = company.address?.city; // undefined
// 深层可选链
const zip = company.address?.zipCode?.value; // undefined,不报错
// ?? 操作符:当左侧为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)
interface User {
name: string;
email?: string; // 可选
age: number | undefined; // 可以是undefined,但不是可选
readonly id: string; // 只读
}
// 初始化时
const user: User = {
name: "Alice",
id: "user-123"
// email和age可以不提供
};

当一个函数的返回值是另一个对象时,继续调用该对象的方法:

// 不使用可选链
const street = user && user.company && user.company.address && user.company.address.street;
// 使用可选链
const street2 = user?.company?.address?.street;
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?.();
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;

// ?? 只在 null/undefined 时使用默认值
null ?? "default"; // "default"
undefined ?? "default"; // "default"
"" ?? "default"; // ""(空字符串不触发)
0 ?? "default"; // 0
false ?? "default"; // false
// || 在所有falsy值时使用默认值
null || "default"; // "default"
undefined || "default"; // "default"
"" || "default"; // "default"
0 || "default"; // "default"
false || "default"; // "default"
// 配置默认值
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(错误!)
// 链式使用
function getPort(config: {
server?: { port?: number };
}): number {
return config.server?.port ?? 8080;
}
getPort({}); // 8080
getPort({ server: {} }); // 8080
getPort({ server: { port: 3000 } }); // 3000

本章学习了TypeScript的复合类型:

  1. 数组类型:

    • number[]或Array<number>声明
    • 丰富的内置方法(map、filter、reduce等)
    • 支持多维数组
  2. 元组类型:

    • 固定长度、固定类型的数组
    • 支持可选元素和剩余元素
    • 解构赋值很方便
  3. 对象类型与接口:

    • interface定义对象结构
    • 支持继承、合并
    • 可选属性、只读属性
  4. 联合类型与交叉类型:

    • 联合类型:|表示或
    • 交叉类型:&表示且
  5. 类型别名:

    • 给复杂类型起别名
    • 支持泛型
  6. 枚举类型:

    • 数字枚举和字符串枚举
    • const枚举编译时内联
  7. null/undefined处理:

    • 可选链?.安全访问
    • 空值合并??提供默认值

创建numbers数组,实现:

  • 过滤出偶数
  • 计算平均值
  • 找出最大值

定义Product接口,包含:

  • id: string(只读)
  • name: string
  • price: number
  • tags?: string[]
  • calculateDiscount(rate: number): number方法

创建Result类型,可以是{ success: true; data: User }或{ success: false; error: string }。编写处理函数。

给Company结构体添加多层可选属性,练习安全访问和默认值设置。

创建一个表示HTTP状态码的枚举,编写根据状态码返回描述的函数。



下一章我们将学习TypeScript的面向对象编程,包括类、继承、访问修饰符、抽象类等概念,与Python的类系统进行对比。