第 102 章:高级类型(Advanced Types)
学习目标
- 掌握交叉类型与联合类型的高级用法
- 学会类型守卫与区分联合类型
- 理解
infer关键字与条件类型 - 掌握模板字面量类型
一、交叉类型(Intersection Types)
把多个类型合并为一个,全部满足。
1.1 基础语法
typescript
type A = { a: string };
type B = { b: number };
// 必须同时有 a 和 b
type AB = A & B;
const obj: AB = {
a: 'hello',
b: 123
};1.2 合并冲突
typescript
// ❌ 同名字段类型不同 → never
type X = { id: string };
type Y = { id: number };
type XY = X & Y;
const id: XY['id']; // 类型: string & number = never
// ✅ 同名字段类型相同 → 取该类型
type P = { name: string };
type Q = { name: string };
type PQ = P & Q; // { name: string }1.3 实战:mixin 模式
typescript
type Loggable = {
log(message: string): void;
};
type Serializable = {
serialize(): string;
};
type Storable = Loggable & Serializable;
class Service implements Storable {
log(message: string) {
console.log(message);
}
serialize() {
return JSON.stringify(this);
}
}二、联合类型(Union Types)
类型可以是其中任意一个。
2.1 基础联合
typescript
type Status = 'pending' | 'success' | 'error';
function setStatus(s: Status) {
// s 是三种之一
}
// 混合类型
type Value = string | number | boolean | null;2.2 可辨识联合(Discriminated Union)
typescript
// 关键:用 kind/ type 字段做"辨识符"
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; sideLength: number }
| { kind: 'rectangle'; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
case 'rectangle':
return shape.width * shape.height;
}
}2.3 穷尽性检查
typescript
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; sideLength: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
default:
// 兜底:加新成员时这里会报错
const _exhaustive: never = shape;
throw new Error(`未知 shape: ${_exhaustive}`);
}
}三、类型守卫(Type Guards)
详细的类型守卫见第 100 章,这里补充一些高级用法。
3.1 自定义类型谓词
typescript
interface Fish {
swim(): void;
kind: 'fish';
}
interface Bird {
fly(): void;
kind: 'bird';
}
function isFish(animal: Fish | Bird): animal is Fish {
return animal.kind === 'fish';
}
function move(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim(); // ✅ Fish
} else {
animal.fly(); // ✅ Bird
}
}3.2 类型谓词 + 泛型
typescript
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isArrayOf<T>(
value: unknown,
check: (item: unknown) => item is T
): value is T[] {
return Array.isArray(value) && value.every(check);
}四、类型收窄(Narrowing)
typescript
function process(value: string | number | null) {
// 1. 真值收窄
if (value) {
// value: string | number
}
// 2. typeof
if (typeof value === 'string') {
// value: string
}
// 3. instanceof
if (value instanceof Date) {
// value: Date
}
// 4. in
if (value && typeof value === 'object' && 'name' in value) {
// value: object & { name: unknown }
}
}五、类型别名 vs 接口的选择
typescript
// ✅ 用 type: 联合、交叉、映射、条件
type Status = 'pending' | 'success' | 'error';
type Mixin = A & B;
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// ✅ 用 interface: 对象结构 + 声明合并
interface User {
name: string;
age: number;
}
interface User {
email: string; // ✅ 自动合并
}六、索引访问类型
typescript
interface User {
name: string;
age: number;
address: {
city: string;
zip: string;
};
}
type UserName = User['name']; // string
type UserAge = User['age']; // number
type UserAddress = User['address']; // { city: string; zip: string }
type CityType = User['address']['city']; // string
// 联合索引
type Fields = User['name' | 'age']; // string | number七、infer 关键字
在条件类型中推断类型,类似类型层面的解构。
7.1 提取函数返回值
typescript
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function foo(): number { return 1; }
type FooReturn = MyReturnType<typeof foo>; // number
function bar(): string { return 'hi'; }
type BarReturn = MyReturnType<typeof bar>; // string7.2 提取参数类型
typescript
type MyParameterType<T> = T extends (arg: infer P) => any ? P : never;
function greet(name: string) { }
type GreetParam = MyParameterType<typeof greet>; // string
// 提取 Promise 的值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number7.3 提取数组元素
typescript
type ElementOf<T> = T extends (infer E)[] ? E : never;
type NumEl = ElementOf<number[]>; // number
type StrEl = ElementOf<string[]>; // string
// 从元组中提取
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type T1 = First<[number, string, boolean]>; // number
type T2 = First<[string]>; // string
type T3 = First<[]>; // never7.4 提取构造函数参数
typescript
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;
class User {
constructor(public name: string, public age: number) {}
}
type UserCtor = ConstructorParameters<typeof User>; // [string, number]八、模板字面量类型(TS 4.1+)
8.1 基础模板
typescript
type Greeting = `hello ${string}`;
const a: Greeting = 'hello world'; // ✅
const b: Greeting = 'hello typescript'; // ✅
const c: Greeting = 'hi world'; // ❌
// 配合联合
type Color = 'red' | 'blue';
type Size = 'small' | 'large';
type Variant = `${Color}-${Size}`;
const v1: Variant = 'red-small'; // ✅
const v2: Variant = 'red-big'; // ❌8.2 大小写转换
typescript
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;
type A = Uppercase<'hello'>; // 'HELLO'
type B = Lowercase<'HELLO'>; // 'hello'
type C = Capitalize<'hello'>; // 'Hello'
type D = Uncapitalize<'Hello'>; // 'hello'8.3 实战:事件名
typescript
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
function bind(event: EventName, handler: `on${Capitalize<EventName>}`) {
// ...
}
bind('click', 'onClick'); // ✅
bind('click', 'onclick'); // ❌九、keyof 与 typeof
9.1 keyof
typescript
interface User {
name: string;
age: number;
}
type UserKeys = keyof User; // 'name' | 'age'
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}9.2 typeof
typescript
const config = {
host: 'localhost',
port: 3000
};
type Config = typeof config;
// { host: string; port: number }9.3 组合使用
typescript
const colors = {
red: '#ff0000',
green: '#00ff00',
blue: '#0000ff'
};
type ColorKey = keyof typeof colors; // 'red' | 'green' | 'blue'
function getColor(key: ColorKey): string {
return colors[key];
}
getColor('red'); // ✅
getColor('yellow'); // ❌十、实战:状态机的类型设计
typescript
// 1. 事件类型
type Event =
| { type: 'FETCH' }
| { type: 'SUCCESS'; data: User[] }
| { type: 'ERROR'; error: Error }
| { type: 'RESET' };
// 2. 状态类型
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; error: Error };
// 3. reducer(强类型)
function reducer(state: State, event: Event): State {
switch (event.type) {
case 'FETCH':
return { status: 'loading' };
case 'SUCCESS':
return { status: 'success', data: event.data };
case 'ERROR':
return { status: 'error', error: event.error };
case 'RESET':
return { status: 'idle' };
}
}十一、本章小结
| 要点 | 关键 |
|---|---|
| 交叉类型 | A & B,同时满足所有类型 |
| 联合类型 | A | B,满足其中之一 |
| 可辨识联合 | 用 kind/type 字段做收窄 |
| 类型守卫 | param is Type 自定义谓词 |
infer | 在条件类型中推断子类型 |
| 模板字面量 | 字符串级别的类型组合 |
keyof / typeof | 提取键名或值的类型 |
动手练习
- 可辨识联合:定义
Result<T>类型({ok: true, value: T}或{ok: false, error: Error}),写一个handle函数 - infer 练习:写一个
MyAwaited<T>类型,提取Promise<T>的内部类型 - 模板字面量:定义
ApiEndpoint类型,接受/users或/users/:id,写一个类型安全的路由函数 - keyof 工具:写一个
RequiredKeys<T>类型,提取 T 中所有必填字段的键名
推荐阅读
- 📖 TypeScript Handbook - Advanced Types — 高级类型官方文档
- 📖 TypeScript Handbook - Template Literal Types — 模板字面量
- 🌐 Type Challenges — 类型体操练习