TypeScript 类型系统
TypeScript 的类型系统是它区别于 JavaScript 的根本特征。理解它的设计哲学——结构化类型(Structural Typing)——是理解 TypeScript 一切类型行为的关键。
跟 Python 的鸭子类型(“有这个方法就能用”)相比,TS 就是”编译期强制的鸭子类型”——形状对得上就兼容,但 tsc 不通过你根本跑不起来。
设计哲学:为什么是结构化类型
与 JS 生态的共生关系
TypeScript 的目标不是重新发明一门语言,而是为已有的 JavaScript 生态加类型注释。十亿行 JS 代码不会为 TS 重写,所以 TS 的类型系统必须容忍 JS 的灵活性。
// 没有任何 interface 声明,纯 JS 对象也能通过类型检查
const user = { name: "Alice", age: 30, email: "[email protected]" };
function greet(p: { name: string; age: number }) {
return `Hello, ${p.name}`;
}
greet(user); // 通过 — user 的形状兼容参数类型Python 鸭子类型 → TS 结构化类型
TS 的结构化类型本质上是 Python 鸭子类型在静态类型系统中的自然延伸:
# Python:鸭子类型 — 运行时只要对象有 .x 和 .y 就能用
def distance(p):
return (p.x ** 2 + p.y ** 2) ** 0.5
# 任何有 x、y 属性的对象都能传进去
class Point:
def __init__(self, x, y): self.x, self.y = x, y
class Vec3:
def __init__(self, x, y, z): self.x, self.y, self.z = x, y, z
distance(Point(3, 4)) #
distance(Vec3(3, 4, 5)) # 多了 .z 也无所谓// TS:结构化类型 — 编译期做同样的事,但不通过编译就不让运行
interface HasXY {
x: number;
y: number;
}
function distance(p: HasXY): number {
return Math.sqrt(p.x ** 2 + p.y ** 2);
}
const pt = { x: 3, y: 4, z: 5 };
distance(pt); // 编译通过 — pt 至少包含 x、yPython 里类型错误在运行时爆;TS 里
tsc不通过你根本跑不起来。这是”灵活的鸭子”变成”有门禁的鸭子”。
结构化类型 vs 标称类型
| 维度 | 结构化类型 (TS) | 鸭子类型 (Python) | 标称类型 (Java/C#) |
|---|---|---|---|
| 兼容判断 | 形状匹配即可 | 有方法/属性就用 | 显式声明 implements 或继承 |
| 检查时机 | 编译期(强制) | 运行时(崩溃才知) | 编译期(强制) |
| 灵活性 | 高 | 极高 | 低 |
| 代表场景 | JSON 响应、配置对象、API | 快速实验、脚本 | 领域模型、微服务边界 |
// TS:多余字段不阻碍赋值(结构兼容)
interface Point {
x: number;
y: number;
}
const p3 = { x: 1, y: 2, z: 3 };
const pt: Point = p3; // 通过 — p3 至少包含 x、y
// Java 则需要:
// class MyPoint implements Point { ... }代价:两个结构相同但语义不同的类型(如 UserId vs ProductId)会被视为兼容。这要靠”品牌化”技巧来模拟名义判断:
type UserId = string & { readonly __brand: "UserId" };
type ProductId = string & { readonly __brand: "ProductId" };
function getUser(id: UserId) { /* ... */ }
const uid = "abc" as UserId;
getUser(uid); // 通过
// getUser("abc"); // 类型错误 — 普通 string 不兼容核心类型工具
interface vs type:选择边界
| 维度 | interface | type |
|---|---|---|
| 声明合并 | ✅ 同名自动合并 | ❌ 报错 |
| 扩展 | extends | & 交叉 |
| 基本类型别名 | ❌ 不支持 | ✅ type Name = string |
| 联合/元组 | ❌ 不支持 | ✅ type A = string | number |
| 性能(大项目) | 更优(缓存) | 略差(每次评估) |
| 社区建议 | 对象形状优先用 | 联合/映射/条件类型用 |
// 对象形状 → interface
interface User {
name: string;
age: number;
}
// 联合类型 → type
type Status = "idle" | "loading" | "success" | "error";
// 函数签名 → type 更简洁
type FetchFn = (url: string) => Promise<Response>;
// 交叉类型扩展
type Admin = User & { role: "admin"; permissions: string[] };联合类型与交叉类型
TS 的联合类型(|)是表达能力最强的特性之一,远超大多数语言的枚举:
// 可辨识联合(Discriminated Union)— TS 的"ADT"
type Result<T> =
| { status: "ok"; data: T }
| { status: "error"; message: string };
function handle<T>(result: Result<T>) {
switch (result.status) {
case "ok":
return result.data; // TS 自动收窄类型
case "error":
return result.message; // 同上
}
}交叉类型(&)合并多个类型的所有属性:
type A = { a: string };
type B = { b: number };
type C = A & B; // { a: string; b: number }泛型:约束而非放任
// 基础泛型
function first<T>(arr: T[]): T {
return arr[0];
}
// 泛型约束 — 要求 T 有 length 属性
function countField<T extends { length: number }>(item: T): number {
return item.length;
}
countField("hello"); // 5
countField([1, 2]); // 2
// countField(42); // number 没有 length
// keyof + 索引访问 — 类型安全的属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // 返回 string
// getProperty(user, "email"); // 编译错误satisfies 运算符:既约束又保留
TS 4.9 引入的 satisfies 解决了”用 as 太粗暴,用注解丢失精度”的困境:
// 普通注解 — 丢失字面量精度
const config: Record<string, string | number> = {
width: 800, // 类型收窄为 number,丢掉了 800
title: "Hello", // 类型收窄为 string
};
config.width.toFixed(); // 报错 — number 没有 toFixed
// satisfies — 约束形状 + 保留细粒度推断
const config2 = {
width: 800,
title: "Hello",
} satisfies Record<string, string | number>;
config2.width; // 类型是 800(字面量),不是 number
config2.title; // 类型是 "Hello"as const 与 const 类型参数
// as const — 冻结为最窄类型
const routes = ["/home", "/about", "/contact"] as const;
// 类型:readonly ["/home", "/about", "/contact"]
// const 类型参数 (TS 5.0+)
function useConfig<const T>(config: T) {
return config;
}
const c = useConfig({ theme: "dark" });
c.theme; // 类型是 "dark",而非 string高级类型体操:边界与克制
三大核心工具
// 条件类型
type IsString<T> = T extends string ? true : false;
// 映射类型 — 基于已有类型生成新类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
// 模板字面量类型 — 字符串级别的类型运算
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type Route = `/api/${string}`;类型体操的边界
社区在 2024–2026 年形成了”健康用法共识”:
| 适合类型体操 | 不适合类型体操 |
|---|---|
| 库/框架的公共 API(如 Zod、tRPC) | 业务组件内部逻辑 |
| 类型层面的代码生成(如根据路由推导参数) | 能用 if/switch 表达的运行时逻辑 |
| 一次性封装的工具类型 | 超过 2 层嵌套的条件类型 |
核心原则:类型系统是”约束”工具,不是”证明”工具。 把复杂校验交给 Zod/Valibot 等运行时 schema 库,TS 类型只描述结构:
import { z } from "zod";
// 运行时校验 + 自动推导 TS 类型
const UserSchema = z.object({
name: z.string(),
age: z.number().min(0).max(150),
});
type User = z.infer<typeof UserSchema>; // 自动从 schema 推导类型与 Python 的对比
| 维度 | TypeScript | Python |
|---|---|---|
| 类型哲学 | 结构化类型 — “形状对得上就行” | 鸭子类型 — “有这个方法就行” |
| 类型检查 | 编译期强制(tsc) | 运行时 + 可选静态(mypy/Pyright) |
| 类型擦除 | ✅ 编译后 JS 不携带类型信息 | ✅ 解释器忽略注解 |
| 联合类型 | ✅ 一等公民 A | B | ⚠️ Union[A, B],不支持可辨识联合 |
| 条件类型 | ✅ T extends U ? X : Y | ❌ 不支持 |
| 泛型体验 | 流畅(<T> 内联) | 改善中(PEP 695 后简化,仍冗长) |
any 逃生门 | ✅ 存在,不应滥用 | ⚠️ 默认无类型约束 |
Python 类型标注的干扰点:你习惯的 Pydantic
BaseModel在运行时保留类型信息并做校验;TS 的类型在运行时完全消失,必须用 Zod/Valibot 做”类型擦除后的校验”。这是 Python → TS 最常见的认知落差。
any vs unknown vs never
// any — 关闭类型检查(逃生门,尽量少用)
let x: any = 42;
x.foo.bar(); // 编译不报错,运行时才崩
// unknown — 安全的"不知道类型"
let y: unknown = 42;
// y.foo(); // 编译报错 — unknown 不能直接操作
if (typeof y === "string") {
y.toUpperCase(); // 收窄后可用
}
// never — 永远不会发生的类型
function throwError(msg: string): never {
throw new Error(msg);
}
type NonNullable<T> = T extends null | undefined ? never : T;Python 视角:any 相当于 Python 的裸变量(无类型约束),unknown 相当于 object(必须先 isinstance 收窄),never 在 Python 中没有直接对应。
关联
- 类型系统 — 跨语言类型系统对比(鸭子/结构/标称)
- Python 面向对象 — Python 的鸭子类型与 OOP
- TS 声明文件与互操作 —
.d.ts如何表达类型 - TS 编译与执行 — 类型擦除的运行时后果
- TS 语言全景 — 结构化类型的优劣总结
- TypeScript 专题 MOC
- 范式: 面向对象编程 — OOP 在 TS 中的类型表达
- 范式: 泛型编程 — TS 的泛型与类型体操