第6章:高级类型(Advanced Types)
一、导读
1.1 本章学习目标
第6章讲解 TypeScript 的高级类型系统——条件类型、映射类型、模板字面量类型等。这些特性让你能够在类型层面进行"编程",创建强大的类型工具。
本章的核心学习目标包括:
- 深入理解条件类型:
T extends U ? X : Y - 掌握映射类型:如何基于已有类型创建新类型
- 学会使用模板字面量类型:在类型层面操作字符串
- 理解 `infer` 关键字:在条件类型中推断类型变量
- 掌握递归条件类型
1.2 为什么本章如此重要
高级类型是 TypeScript 类型系统的"天花板"。掌握它们,你就能在类型层面实现复杂的逻辑——从 JSON 字符串推断出类型、从路由路径推断出参数类型、从 API 响应推断出数据结构。这些能力让 TypeScript 的类型系统成为一个强大的"类型级编程语言"。
1.3 核心问题引导
条件类型是如何工作的?什么是分布式条件类型?
infer 关键字的作用是什么?它能推断哪些位置的类型?
模板字面量类型能做什么?它和普通字符串字面量有什么区别?
递归条件类型有什么限制?如何避免无限递归?
二、核心概念详解
2.1 条件类型深入
// 基本形式
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false
// 分布式条件类型:对联合类型的每个成员分别应用
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[]
// 非分布式:用元组包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type D = ToArrayNonDist<string | number>; // (string | number)[]2.2 infer 关键字
// 推断函数返回类型
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = GetReturnType<() => string>; // string
type R2 = GetReturnType<(x: number) => boolean>; // boolean
// 推断 Promise 的内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type P1 = UnwrapPromise<Promise<string>>; // string
type P2 = UnwrapPromise<number>; // number
// 推断构造函数参数
type ConstructorParams<T> = T extends new (...args: infer P) => any ? P : never;
class MyClass {
constructor(public name: string, public age: number) {}
}
type Params = ConstructorParams<typeof MyClass>; // [string, number]2.3 映射类型
// 基本映射类型
type Readonly2<T> = { readonly [K in keyof T]: T[K] };
type Partial2<T> = { [K in keyof T]?: T[K] };
// 键重映射(key remapping)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
// 过滤属性
type RemoveNull<T> = {
[K in keyof T as T[K] extends null | undefined ? never : K]: T[K]
};2.4 模板字面量类型
// 基本用法
type EventName = `on${Capitalize<string>}`;
// 联合类型组合
type Color = "red" | "blue" | "green";
type Size = "sm" | "md" | "lg";
type ButtonClass = `${Color}-${Size}`;
// "red-sm" | "red-md" | "red-lg" | "blue-sm" | ...
// 路由参数提取
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params1 = ExtractRouteParams<"/users/:id">; // "id"
type Params2 = ExtractRouteParams<"/users/:id/posts/:postId">; // "id" | "postId"2.5 递归条件类型
// 深度 Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K];
};
// 深度 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? T[K] extends Function
? T[K]
: DeepPartial<T[K]>
: T[K];
};
// Flatten:展平嵌套数组
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type F1 = Flatten<number[][][]>; // number三、实战案例
3.1 案例一:类型安全的路由系统
type RouteMap = {
"/users": { userId?: never };
"/users/:id": { id: string };
"/users/:id/posts/:postId": { id: string; postId: string };
"/settings": {};
};
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type RouteParams<T extends keyof RouteMap> = ExtractParams<T>;
function navigate<T extends keyof RouteMap>(
route: T,
params: RouteParams<T>
) {
let path = route as string;
for (const [key, value] of Object.entries(params)) {
path = path.replace(`:${key}`, value);
}
console.log(`Navigating to: ${path}`);
}
navigate("/users/:id", { id: "123" });
navigate("/users/:id/posts/:postId", { id: "123", postId: "456" });
navigate("/settings", {});3.2 案例二:类型安全的 JSON 解析器
type JsonPrimitive = string | number | boolean | null;
type JsonObject = { [key: string]: JsonValue };
type JsonArray = JsonValue[];
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
type JsonToType<T extends string> =
T extends "null" ? null :
T extends "true" ? true :
T extends "false" ? false :
T extends `${number}` ? number :
T extends `"${infer S}"` ? S :
T extends `[${infer Items}]` ? JsonArrayToType<Items> :
T extends `{${infer Props}}` ? JsonObjectToType<Props> :
never;
type Trim<S extends string> = S extends ` ${infer T}` ? Trim<T> :
S extends `${infer T} ` ? Trim<T> : S;四、本章小结
条件类型:T extends U ? X : Y,分布式条件类型对联合类型逐成员应用。
infer:在条件类型中推断类型变量,用于提取函数返回类型、Promise 内部类型等。
映射类型:{ [K in keyof T]: ... } 基于已有类型创建新类型,支持键重映射。
模板字面量类型:在类型层面操作字符串,实现路由参数提取等高级功能。
递归条件类型:实现 DeepReadonly、DeepPartial 等递归类型操作。
五、思考与练习
分布式条件类型和非分布式条件类型有什么区别?各适用于什么场景?
实现一个 DeepMutable<T> 工具类型,递归地移除所有 readonly 修饰符。
实现一个类型安全的模板引擎类型,从模板字符串中提取变量名。