第 4 章:高级类型编程¶
学习时间:5 小时 | 难度:⭐⭐⭐⭐ 高级 | 前置知识:第 2-3 章,理解泛型基础
本章概览¶
本章是 TypeScript 类型体操的核心——掌握这些工具,你可以用类型系统表达几乎任何约束,构建零运行时开销的类型安全库。
学习目标:
- 掌握泛型约束(extends)和默认类型参数
- 理解条件类型(Conditional Types)及
infer关键字 - 掌握映射类型(Mapped Types)和同态映射
- 运用模板字面量类型(Template Literal Types)
- 理解协变与逆变(Covariance/Contravariance)
- 实战:手写常见工具类型的实现
4.1 泛型进阶¶
TypeScript
// ── 泛型约束(extends) ──────────────────────────────
// 约束 T 必须具有 length 属性
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "world"); // string(OK)
longest([1, 2], [3]); // number[](OK)
// longest(1, 2); // ❌ number 没有 length 属性
// 约束 K 必须是 T 的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // 100% 类型安全的属性访问
}
const user = { name: "Alice", age: 30, active: true };
getProperty(user, "name"); // 返回 string
getProperty(user, "age"); // 返回 number
// getProperty(user, "xxx"); // ❌ 错误:"xxx" 不是 user 的键
// ── 多个泛型参数 ────────────────────────────────────
function merge<T extends object, U extends object>(target: T, source: U): T & U {
return { ...target, ...source };
}
const merged = merge({ name: "Alice" }, { role: "admin" });
// merged: { name: string } & { role: string }
console.log(merged.name, merged.role); // 完整类型支持
// ── 泛型默认类型参数 ─────────────────────────────────
interface ApiClient<TBase = string> {
baseUrl: TBase;
get<TResponse>(path: string): Promise<TResponse>;
post<TBody, TResponse>(path: string, body: TBody): Promise<TResponse>;
}
// 不传 TBase 时默认为 string
const client: ApiClient = { baseUrl: "https://api.example.com", get: async () => ({}), post: async () => ({}) };
// ── 递归泛型:深层嵌套类型 ───────────────────────────
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Config {
server: { host: string; port: number };
database: { url: string; pool: { min: number; max: number } };
}
const config: DeepReadonly<Config> = {
server: { host: "localhost", port: 3000 },
database: { url: "postgres://...", pool: { min: 2, max: 10 } },
};
// config.server.host = "x"; // ❌ 错误:深层只读
4.2 条件类型(Conditional Types)¶
条件类型让类型系统可以做"判断":T extends U ? TrueType : FalseType
TypeScript
// ── 基础条件类型 ─────────────────────────────────────
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
type C = IsString<"hello">; // "yes"(字面量类型也满足 extends string)
// ── 分布式条件类型(Distributive Conditional Types) ──
// 当 T 是裸类型参数时,条件类型会自动分发到联合类型的每个成员
type NonNullable<T> = T extends null | undefined ? never : T;
// NonNullable<string | null | number> = string | number(分别分发再合并)
type ToArray<T> = T extends unknown ? T[] : never;
// ToArray<string | number> = string[] | number[](分布式!)
// 如果不想分布式,用方括号包裹:
type ToArrayNonDistributive<T> = [T] extends [unknown] ? T[] : never;
// ToArrayNonDistributive<string | number> = (string | number)[]
// ── 实用工具类型手写 ─────────────────────────────────
// 提取函数类型(内置 ReturnType 的实现原理)
type MyReturnType<T extends (...args: unknown[]) => unknown> =
T extends (...args: unknown[]) => infer R ? R : never;
// 提取 Promise 的泛型参数(内置 Awaited 的实现原理)
type MyAwaited<T> =
T extends Promise<infer Inner>
? Inner extends Promise<unknown>
? MyAwaited<Inner> // 递归处理嵌套 Promise
: Inner
: T;
type P1 = MyAwaited<Promise<string>>; // string
type P2 = MyAwaited<Promise<Promise<number>>>; // number(递归展开!)
// 过滤联合类型中的函数类型属性
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never;
}[keyof T];
interface UserService {
id: number;
name: string;
getProfile(): Promise<void>;
updateProfile(data: Partial<{ name: string }>): Promise<void>;
}
type Methods = FunctionKeys<UserService>; // "getProfile" | "updateProfile"
4.3 infer 关键字¶
infer 是条件类型的超能力——在类型匹配时"捕获"类型:
TypeScript
// ── 提取数组元素类型 ─────────────────────────────────
type ElementType<T> = T extends (infer E)[] ? E : never;
type N = ElementType<number[]>; // number
type S = ElementType<string[]>; // string
type M = ElementType<(string | number)[]>; // string | number
// ── 提取函数参数类型 ─────────────────────────────────
type FirstParam<T extends (...args: any[]) => any> =
T extends (first: infer F, ...rest: any[]) => any ? F : never;
type F = FirstParam<(name: string, age: number) => void>; // string
// ── 提取类构造函数参数 ───────────────────────────────
type ConstructorParams<T extends new (...args: any[]) => any> =
T extends new (...args: infer P) => any ? P : never;
class HttpClient {
constructor(baseUrl: string, timeout: number) {}
}
type Params = ConstructorParams<typeof HttpClient>; // [baseUrl: string, timeout: number]
// ── 路径字符串类型提取(模板字面量 + infer 组合)──────
type ExtractRouteParams<S extends string> =
S extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: S extends `${string}:${infer Param}`
? Param
: never;
type Params2 = ExtractRouteParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"
// 实际应用:类型安全的路由参数
type RouteHandler<Path extends string> = (
params: Record<ExtractRouteParams<Path>, string>
) => void;
const handler: RouteHandler<"/users/:userId/posts/:postId"> = (params) => {
console.log(params.userId, params.postId); // 完整类型支持
// console.log(params.xxx); // ❌ 错误:"xxx" 不在参数中
};
4.4 映射类型(Mapped Types)¶
TypeScript
// ── 语法:[K in keyof T] ─────────────────────────────
type Stringify<T> = {
[K in keyof T]: string; // 把所有属性类型改为 string
};
// ── 添加/删除修饰符 ──────────────────────────────────
// + (默认): 添加 readonly 或 ?
// - : 删除 readonly 或 ?
type Mutable<T> = {
-readonly [K in keyof T]: T[K]; // 删除所有 readonly
};
type Required<T> = {
[K in keyof T]-?: T[K]; // 删除所有 ?(变为必填)
};
// ── 同态映射(Homomorphic Mapped Types)────────────────
// 保留原有修饰符,只改变类型
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// K as 重映射(Key Remapping, TS 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User { name: string; age: number; email: string }
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; getEmail: () => string }
// 过滤属性(K as 结合条件类型)
type PickByValue<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K];
};
interface Mixed {
id: number;
name: string;
active: boolean;
createdAt: Date;
}
type StringProps = PickByValue<Mixed, string>; // { name: string }
type NumberProps = PickByValue<Mixed, number>; // { id: number }
// ── 实战:构建不可变的深层对象 API ───────────────────
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
function mergeDeep<T>(target: DeepPartial<T>, source: DeepPartial<T>): DeepPartial<T> {
const result = { ...target };
for (const key in source) {
if (typeof source[key] === "object" && source[key] !== null) {
result[key] = mergeDeep(result[key] ?? {}, source[key]) as any;
} else {
result[key] = source[key];
}
}
return result;
}
4.5 模板字面量类型(Template Literal Types)¶
TypeScript
// ── 基础模板字面量 ────────────────────────────────────
type Greeting = `Hello, ${string}!`; // 匹配所有 "Hello, xxx!" 格式的字符串
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`; // "onClick" | "onFocus" | "onBlur"
// ── 构建类型安全的 CSS 类名 ──────────────────────────
type Size = "sm" | "md" | "lg" | "xl";
type Color = "primary" | "secondary" | "danger";
type ButtonClass = `btn-${Color}-${Size}`;
// "btn-primary-sm" | "btn-primary-md" | "btn-secondary-sm" | ... 16 个组合
// ── 构建事件系统类型 ─────────────────────────────────
type EventMap = {
click: { x: number; y: number };
keypress: { key: string; code: number };
resize: { width: number; height: number };
};
type OnEventKeys = `on${Capitalize<keyof EventMap>}`; // "onClick" | "onKeypress" | "onResize"
// 类型安全的事件发射器
class TypedEventEmitter<TEvents extends Record<string, unknown>> {
private listeners = new Map<string, Function[]>();
on<K extends keyof TEvents & string>(
event: K,
listener: (data: TEvents[K]) => void
): this {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(listener);
return this;
}
emit<K extends keyof TEvents & string>(event: K, data: TEvents[K]): void {
this.listeners.get(event)?.forEach((fn) => fn(data));
}
}
const emitter = new TypedEventEmitter<{
userLogin: { userId: string; ip: string };
userLogout: { userId: string };
pageView: { url: string; referrer: string };
}>();
emitter.on("userLogin", ({ userId, ip }) => {
console.log(`User ${userId} logged in from ${ip}`); // 完整类型支持
});
// emitter.on("unknown", () => {}); // ❌ 错误:"unknown" 不在事件映射中
// emitter.emit("userLogin", { userId: "1" }); // ❌ 错误:缺少 ip 字段
4.6 协变与逆变¶
TypeScript
// 协变(Covariance):子类型可以赋值给父类型
// ← 输出位置(return type)是协变的
type Producer<T> = () => T;
const stringProducer: Producer<string> = () => "hello";
// Producer<string> 可以赋值给 Producer<string | number> 吗?
// YES:返回 string 的函数可以赋值给返回 string | number 的位置
// (因为 string 是 string | number 的子类型)
// 逆变(Contravariance):父类型可以赋值给子类型
// → 输入位置(parameter type)是逆变的
type Consumer<T> = (value: T) => void;
const numberConsumer: Consumer<number> = (n) => console.log(n * 2);
// Consumer<string | number> 可以赋值给 Consumer<string> 吗?
// YES:接受 string | number 的函数,肯定也能接受 string
// 实际例子
type Handler<E extends Event> = (event: E) => void;
// Handler<MouseEvent> extends Handler<Event>?
// MouseEvent extends Event(MouseEvent 是子类型)
// 但 Handler 是逆变的:Handler<Event> extends Handler<MouseEvent>(反过来!)
// 为什么重要?函数参数类型检查
function processEvent(handler: (event: Event) => void) {
handler(new MouseEvent("click")); // 传入 MouseEvent
}
// ✅ 接受 Event(父类型)的函数可以传进来
processEvent((e: Event) => console.log(e.type));
// ❌ 接受 MouseEvent(子类型)的函数不能传进来
// (它假设 e 有 MouseEvent 的方法,但可能收到其他 Event 类型的值)
// processEvent((e: MouseEvent) => e.clientX); // 不安全!
4.7 实战:手写常见工具类型¶
TypeScript
// 手写 Exclude<T, U>
type MyExclude<T, U> = T extends U ? never : T;
type E = MyExclude<"a" | "b" | "c", "b">; // "a" | "c"
// 手写 Extract<T, U>
type MyExtract<T, U> = T extends U ? T : never;
type X = MyExtract<string | number | boolean, string | boolean>; // string | boolean
// 手写 Pick<T, K>
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 手写 Omit<T, K>
type MyOmit<T, K extends keyof T> = MyPick<T, Exclude<keyof T, K>>;
// 手写 Parameters<T>
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
// 手写 ReturnType<T>
type MyReturnType2<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : any;
// 进阶:UnionToIntersection(将联合转交叉)
type UnionToIntersection<U> =
(U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
type I = UnionToIntersection<{ a: string } | { b: number } | { c: boolean }>;
// { a: string } & { b: number } & { c: boolean }
// 进阶:TupleToUnion
type TupleToUnion<T extends readonly unknown[]> = T[number];
type TU = TupleToUnion<["a", "b", "c"]>; // "a" | "b" | "c"
// 进阶:Prettify(展开嵌套类型,改善 IDE 显示)
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type Complex = { a: string } & { b: number } & { c: boolean };
type Simple = Prettify<Complex>; // { a: string; b: number; c: boolean }(展开显示)
🏋️ 本章练习¶
- 条件类型:实现
DeepRequired<T>,递归地将所有属性变为必填 - infer 实战:实现
UnpackPromise<T>,支持提取Promise<Promise<Promise<string>>>最深层类型 - 映射类型:实现
EventHandlers<T>,将{ click: MouseEvent; keydown: KeyboardEvent }转换为{ onClick: (e: MouseEvent) => void; onKeydown: (e: KeyboardEvent) => void }
TypeScript 5.8 · 类型编程指南 · 2025