Effective TypeScript
将类型视为值的集合
在 TypeScript 进行静态分析时,一个变量的类型(Type)最好被视为一组可能值的集合。这个集合被称为该类型的领域 (domain)。
理解类型作为集合这一概念,对于解释赋值性(assignability)和类型操作至关重要。
集合的边界:never 与 unknown
类型集合存在于两个极值:
1. 底部类型 (never)
never对应于空集 (),它不包含任何值。- 因为其领域是空的,所以任何值都不能赋值给一个
never类型的变量。never有时被称为“底部类型”(bottom type)。
// 示例:底部类型
type = never;
const x: = 12; 2. 顶部类型 (unknown)
unknown对应于全集(Universal set),包含 JavaScript 中所有的值。- 所有其他类型都可以被赋值给
unknown。unknown被称为“顶部类型”(top type)。
构建集合:字面量类型与联合类型
1. 字面量类型 (Literal Types)
包含单个值的集合对应于 字面量类型:
type Status = 'success'; // 集合 {'success'}
type Code = 200; // 集合 {200}2. 联合类型 (Union Types)
使用管道符 (|) 创建的联合类型,其领域是其组成类型领域的并集(Union)。
type StatusCode = 200 | 404 | 500;
// 集合 {200, 404, 500}.集合关系:赋值性与子类型
赋值性 (Assignability)
在集合语境中,类型检查器主要测试两个关系:
- 值与类型: 检查值是否是该集合的成员(member of)。
- 类型与类型: 检查一个类型是否是另一个类型的子集(subset of)。
type = 'A' | 'B';
// 'A' 是 AB 集合的成员
const : = 'A'; // OK.
// "C" 集合不是 AB 集合的子集,因此赋值失败
const c: = 'C'; 子类型、继承与 extends
- 在 TypeScript 中,
extends、assignable to(可赋值给)和subtype of(子类型)这几个术语都可以被理解为 “子集”。 - 类型关系可以被视为重叠的集合(Venn diagram),而非严格的继承层级结构。
interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
// Vector2D 集合是 Vector1D 集合的子集。当你在泛型约束中使用 extends 时,它同样表示“子集”关系。例如,K extends string 意味着类型 K 的领域必须是 string 领域的子集(例如字面量类型或联合类型)。
结构化类型与无限领域
接口与无限集
实际应用中的大多数类型都具有无限领域。接口(interface)定义了构成其领域内值的结构。
由于 TypeScript 采用结构化类型(structural typing,Item 4),一个值即使拥有接口中未声明的额外属性,它仍然属于该类型。因此,类型被认为是“开放的”(open),而不是“封闭的”(sealed)。
交叉类型 (Intersection Types)
&操作符计算的是两个类型的交集(Intersection)。- 与直觉不同的是,交叉类型
A & B的值必须包含 和 所有属性的并集。interface Person { name: string; } interface Lifespan { birth: Date; } type PersonSpan = Person & Lifespan; // 一个值只有同时拥有 Person 和 Lifespan 的属性,才属于 PersonSpan 集合. const ps: PersonSpan = { name: 'Alan Turing', birth: new Date('1912/06/23'), }; // OK.
keyof 集合关系
集合的思维模式有助于理解 keyof 运算符对联合类型和交叉类型的操作:
keyof (A & B)等价于(keyof A) | (keyof B)(属性类型的并集)。keyof (A | B)等价于(keyof A) & (keyof B)(只有在所有联合成员中都存在的属性才会被保留,即属性类型的交集)。
重要提示:readonly 的影响
将类型视为值的集合这一解释在处理不可变值时最为有效。
然而,一个重要的限制是,readonly(Item 14)等修饰符可能会使两个拥有相同值集合的类型在可操作性上产生差异。例如:
interface Lockbox { code: number; }
interface ReadonlyLockbox { readonly code: number; }
// 这两个类型的值域(domain)完全相同,但对后者你不能进行赋值操作。因此,该项目建议的一个变体是:“类型是值的集合,以及你可以用它们做的事情”。
集合术语与 TypeScript 术语对照表
| TypeScript 术语 | 集合术语 | 引用 |
|---|---|---|
never | 空集 () | |
| 字面量类型 (Literal type) | 单元素集合 | |
| 值可赋值给 | 值 (成员) | |
| 可赋值给 | (子集) | |
extends | (子集) | |
| | (并集/联合) | |
& | (交集) | |
unknown | 全集(Universal set) |
在 GitHub 上编辑
上次更新于