Skip to content

第12章 类型编程深度

本章定位:深入TypeScript类型系统的核心,理解类型运算的本质,掌握infer关键字、递归类型计算,以及类型级别的FizzBuzz等高级技巧。


TypeScript的类型本质上是一组值的集合:

// string类型 = 所有可能的字符串集合
type StringSet = string; // "a", "b", "hello", ...
// number类型 = 所有可能的数字集合
type NumberSet = number; // 1, 2, 3, 3.14, -5, ...
// never类型 = 空集合(没有任何值)
type EmptySet = never;
// unknown类型 = 所有可能值的全集
type UniversalSet = unknown;
// 联合类型 = 集合的并集
type Union = string | number; // 所有字符串和所有数字的并集
// 交叉类型 = 集合的交集
type Intersection = string & number; // never(没有值既是string又是number)

TypeScript使用结构类型(Structural Typing),只关心类型的结构,不关心类型的名称:

interface Point2D {
x: number;
y: number;
}
interface Point2DAlias {
x: number;
y: number;
}
// Point2D和Point2DAlias是同一类型
const p: Point2D = { x: 0, y: 0 };
const p2: Point2DAlias = p; // OK
// unknown是所有类型的父类型
type All = unknown;
// never是所有类型的子类型
type Nothing = never;
// any是特殊的,既是所有类型的父类型也是子类型
type Anything = any;

TypeScript通过结构等价判断类型相等:

type A = { x: number };
type B = { x: number };
// A和B是同一类型
type Same = A extends B ? (B extends A ? true : false) : false;
// true

12.2 类型运算:交集、并集、差集

Section titled “12.2 类型运算:交集、并集、差集”
// 并集 (Union)
type Union = string | number; // "a" | 1 | "b" | 2 | ...
// 交集 (Intersection)
type Intersection = { name: string } & { age: number };
// { name: string; age: number }
// 差集 - 使用Exclude
type StringOrNumber = string | number;
type NotString = Exclude<StringOrNumber, string>; // number
// 相对补集 - 使用Extract
type StringOrNumber2 = string | number | boolean;
type OnlyNumber = Extract<StringOrNumber2, number | string>; // string | number
type Difference<T, U> = T extends U ? never : T;
type A = "a" | "b" | "c";
type B = "b" | "c";
type Result = Difference<A, B>; // "a"
type SymmetricDifference<T, U> = Difference<T, U> | Difference<U, T>;
type A = "a" | "b" | "c";
type B = "b" | "c" | "d";
type Result = SymmetricDifference<A, B>; // "a" | "d"
type PowerSet<T extends string> = T extends any
? T | `${T},${PowerSet<Exclude<T, T>>}`
: never;
type Result = PowerSet<"a" | "b">; // "a" | "b" | "a,b"

infer在条件类型中声明一个类型变量:

// 从T中提取Promise resolve后的类型
type Awaited<T> = T extends Promise<infer R> ? R : T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<number>; // number
type C = Awaited<Promise<Promise<number>>>; // number(递归)
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
function createUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof createUser>; // { id: number; name: string }
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type A = Parameters<(name: string, age: number) => void>; // [string, number]
type ConstructorParameters<T extends new (...args: any) => any> =
T extends new (...args: infer P) => any ? P : never;
class User {
constructor(
public name: string,
public age: number
) {}
}
type Params = ConstructorParameters<typeof User>; // [string, number]
type ElementType<T> = T extends (infer E)[] ? E : never;
type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type PropertyType<T, K extends keyof T> = T extends { [P in K]: infer R } ? R : never;
interface User {
name: string;
age: number;
}
type NameType = PropertyType<User, "name">; // string
// 深度展开Promise
type DeepAwaited<T> = T extends Promise<infer U>
? DeepAwaited<U>
: T;
type A = DeepAwaited<Promise<Promise<Promise<string>>>>; // string

类型可以引用自身:

// 链表
interface ListNode<T> {
value: T;
next?: ListNode<T>;
}
type StringList = ListNode<string>;
// { value: string; next?: { value: string; next?: ... } }
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interface Config {
server: {
host: string;
port: number;
};
database: {
credentials: {
username: string;
password: string;
};
};
}
type PartialConfig = DeepPartial<Config>;
// { server?: { host?: string; port?: number; }; database?: { credentials?: { username?: string; password?: string; } } }
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type FrozenConfig = DeepReadonly<Config>;
// 所有嵌套属性都是readonly
type JSONPrimitive = string | number | boolean | null;
type JSONValue = JSONPrimitive | JSONValue[] | { [key: string]: JSONValue };
type JSONObject = { [key: string]: JSONValue };
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type A = Flatten<string[]>; // string
type B = Flatten<string[][]>; // string
type C = Flatten<string[][][]>; // string
type D = Flatten<string | string[]>; // string
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;

type FizzBuzz<N extends number> =
`${N}` extends `${infer _Fizz}${infer _Buzz}`
? _Fizz extends ""
? never
: _Buzz extends ""
? never
: `${_Fizz}${_Buzz}`
: never;
// 或者更直接的实现
type Mod3<N extends number> = N extends 3 | 6 | 9 ? true : false;
type Mod5<N extends number> = N extends 5 | 10 ? true : false;
type FizzBuzz2<N extends number> =
Mod3<N> extends true
? Mod5<N> extends true
? "FizzBuzz"
: "Fizz"
: Mod5<N> extends true
? "Buzz"
: `${N}`;
type IsPrime<N extends number, T extends number[] = []> =
N extends 1 | 4 | 6 | 8 | 9 | 10
? false
: N extends 2 | 3 | 5 | 7
? true
: T["length"] extends N
? true
: N extends T["length"]
? false
: IsPrime<N, [...T, never]>;
type A = IsPrime<2>; // true
type B = IsPrime<15>; // false
type C = IsPrime<17>; // true
type Push<T extends any[], U> = [...T, U];
type A = Push<[1, 2], 3>; // [1, 2, 3]
type Pop<T extends any[]> =
T extends [...infer Rest, infer Last] ? Rest : never;
type B = Pop<[1, 2, 3]>; // [1, 2]
type Map<T extends any[], F> =
T extends [] ? [] :
T extends [infer First, ...infer Rest]
? [F, ...Map<Rest, F>] : never;
type A = Map<[1, 2, 3], string>; // [string, string, string]
type Double<N extends number> = N;
type B = Map<[1, 2, 3], Double<number>>; // [number, number, number]
type Filter<T extends any[], P> =
T extends [] ? [] :
T extends [infer First, ...infer Rest]
? First extends P
? [First, ...Filter<Rest, P>]
: Filter<Rest, P>
: never;
type A = Filter<[1, "a", 2, "b", 3], number>; // [1, 2, 3]

// 数字到字符串
type ToString<N extends number> = `${N}`;
// FizzBuzz规则
type FizzBuzz<N extends number> =
[ToString<N>] extends [`${infer _Fizz}${infer _Buzz}`]
? _Fizz extends ""
? _Buzz extends ""
? ToString<N>
: never
: _Buzz extends ""
? never
: "FizzBuzz"
: "Fizz" | "Buzz" extends `${N}`
? never
: `${N}`;
// 生成序列
type Range<N extends number, R extends any[] = []> =
R["length"] extends N
? R
: Range<N, [...R, R["length"]]>;
type Sequence = Range<20>;
// [0, 1, 2, ..., 19]
type FizzBuzzSequence = {
[K in keyof Sequence]: FizzBuzz<Sequence[K] extends number ? Sequence[K] : never>;
};
// ["0", "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz", ...]
type FB<N extends number> =
N extends 15 ? "FizzBuzz" :
N extends 5 | 10 ? "Buzz" :
N extends 3 | 6 | 9 ? "Fizz" :
`${N}`;
type A = FB<1>; // "1"
type B = FB<3>; // "Fizz"
type C = FB<5>; // "Buzz"
type D = FB<15>; // "FizzBuzz"

// API端点定义
interface API {
"/users": { response: User[] };
"/users/:id": { params: { id: string }; response: User };
"/posts": { body: Post; response: Post };
}
// 工具类型
type ParseParams<T extends string> =
T extends `${infer _}:${infer Param}/${infer Rest}`
? Param | ParseParams<`/${Rest}`>
: T extends `${infer _}:${infer Param}`
? Param
: never;
type ExtractParams<T extends string> = {
[K in ParseParams<T>]: string;
};
type GetResult<T extends string, API> =
T extends keyof API ? API[T]["response"] : never;
// 使用
async function apiGet<T extends keyof API>(
endpoint: T,
params?: ExtractParams<T>
): Promise<GetResult<T, API>> {
// 实现
return {} as any;
}
// 自动补全和类型检查
const users = await apiGet("/users");
const user = await apiGet("/users/:id", { id: "123" });
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Entity = "users" | "posts" | "comments";
type Chain = `${Method} /${Entity}`;
type Routes = {
[K in Chain]: () => Promise<any>;
};
class API {
private routes: Routes = {} as Routes;
route<K extends Chain>(chain: K): Routes[K] {
return this.routes[chain];
}
// 链式方法
get(entity: Entity): this {
// 实现
return this;
}
}
const api = new API();
api.get("users").execute();
type EventMap = {
"user:created": { id: string; name: string };
"user:deleted": { id: string };
"post:published": { id: string; title: string };
};
class TypedEmitter<T extends Record<string, any>> {
private listeners: Partial<{ [K in keyof T]: Set<(data: T[K]) => void> }> = {};
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this {
if (!this.listeners[event]) {
this.listeners[event] = new Set();
}
this.listeners[event]!.add(listener);
return this;
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.listeners[event]?.forEach(listener => listener(data));
}
}
const emitter = new TypedEmitter<EventMap>();
emitter.on("user:created", (data) => {
console.log(data.id, data.name); // 类型安全!
});
emitter.emit("user:created", { id: "123", name: "Alice" });

本章深入了TypeScript类型编程的深度:

  1. 类型系统本质:

    • 类型是值的集合
    • 结构类型系统
    • unknown是全集,never是空集
  2. 类型运算:

    • 并集(|)、交集(&)
    • 差集(Exclude)、对称差集
    • 集合操作在类型中的应用
  3. infer关键字:

    • 在条件类型中声明类型变量
    • 提取函数返回类型、参数类型
    • 提取数组元素、对象属性
  4. 递归类型:

    • 类型可以引用自身
    • 实现DeepPartial、DeepReadonly
    • 深度展开类型
  5. 类型级编程:

    • 编译时计算
    • FizzBuzz、Prime Check
    • Push/Pop、Map、Filter
  6. 类型安全API:

    • 类型安全的API客户端
    • 链式调用设计
    • TypedEventEmitter

实现Intersection<T, U>计算两个对象类型的交集。

实现First<T>提取数组第一个元素的类型。

实现DeepRequired<T>将嵌套属性都设为必填。

实现Sort<T>在类型级别排序数字数组。

设计一个类型安全的路由系统。



下一章我们将学习TypeScript的运行时类型检查,了解如何使用zod、yup等库进行运行时验证。