TypeScript 类型系统为何强大
TypeScript 之所以能在 2026 年依旧是前端与 Node.js 生态的绝对主流,关键在于它的类型系统是“图灵完备级别”的表达能力。它不仅能描述数据的形状,还能在类型层面进行逻辑运算:条件判断、递归、映射、推导。掌握高级类型编程,意味着你能让编译器帮你拦下大量运行时 bug。
本文从泛型出发,一步步走向条件类型、infer 推导、映射类型与模板字面量类型,最后给出一个实战工具类型库。
泛型:类型层面的参数化
泛型让函数、接口、类可以“延迟指定类型”。最经典的例子是 identity 函数:
function identity<T>(value: T): T {
return value;
}
const n = identity(42); // 推导为 number
const s = identity("hello"); // 推导为 string泛型真正的威力在于约束。用 extends 限制类型参数的范围:
// 要求 T 必须包含 length 属性
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("abc"); // OK
logLength([1, 2, 3]); // OK
// logLength(123); // Error: number 没有 length泛型还可以有默认值和多个参数,常用于工厂函数与 API 封装:
interface ApiResponse<T = unknown, E = string> {
data: T;
error: E | null;
code: number;
}
function request<T>(url: string): Promise<ApiResponse<T>> {
/* ... */
return null as any;
}keyof 与映射类型
keyof 取出一个对象类型的所有键组成的联合类型,是类型编程的基石。
interface User {
id: number;
name: string;
email: string;
}
type UserKeys = keyof User; // "id" | "name" | "email"配合映射类型,可以批量转换属性。TypeScript 内置了 Partial、Readonly、Pick、Omit,理解它们的实现有助于自定义:
// 手写 Partial
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 把所有属性变成可空(null | undefined)
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// 把所有属性变成只读 getter
type ReadonlyAll<T> = {
readonly [K in keyof T]: T[K];
};条件类型与 infer
条件类型形如 T extends U ? X : Y,类似三元表达式,但作用在类型层面。它让类型具备“判断”能力。
type IsString<T> = T extends string ? true : false;
type A = IsString<"hi">; // true
type B = IsString<42>; // falseinfer 关键字是条件类型的灵魂,它能在条件分支中“捕获”某个类型变量。最经典的例子是提取函数返回类型与 Promise 内部类型:
// 提取函数返回值类型(等价于内置 ReturnType)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 提取 Promise 的内部类型
type Unpacked<T> = T extends Promise<infer U> ? U : T;
type R1 = MyReturnType<() => number>; // number
type R2 = Unpacked<Promise<string>>; // string
type R3 = Unpacked<Promise<Promise<number>>>; // Promise<number> (非递归)递归条件类型
条件类型可以递归,从而处理嵌套结构。下面实现一个深度 Unpacked 与扁平化元组:
// 深度解包 Promise
type DeepUnpacked<T> = T extends Promise<infer U> ? DeepUnpacked<U> : T;
type D = DeepUnpacked<Promise<Promise<Promise<number>>>>; // number
// 元组扁平化
type Flatten<T extends readonly any[]> = T extends [infer Head, ...infer Tail]
? Head extends readonly any[]
? [...Flatten<Head>, ...Flatten<Tail>]
: [Head, ...Flatten<Tail>]
: [];
type F = Flatten<[1, [2, 3], [[4]]]>; // [1, 2, 3, 4]注意递归类型在 TypeScript 中有深度限制(约 50 层),过深的递归会导致“Type instantiation is excessively deep”报错。
模板字面量类型
模板字面量类型让你在类型层面拼接字符串,特别适合生成 getter/setter、事件名等。
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 Join<K, P> = K extends string | number
? P extends string | number
? `${K}${"" extends P ? "" : "."}${P}`
: never
: never;
type Paths<T, P extends string = ""> = T extends object
? {
[K in keyof T]: K extends string
? Join<"" extends P ? K : `${P}.${K}`, "" >
: never;
}[keyof T]
: never;实战:类型安全的事件系统
结合上述技巧,构建一个类型安全的事件总线:
type EventMap = {
login: { userId: string; time: number };
logout: { userId: string };
purchase: { orderId: string; amount: number };
};
class TypedEmitter<T extends Record<string, any>> {
private handlers: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};
on<K extends keyof T>(event: K, cb: (payload: T[K]) => void) {
(this.handlers[event] ??= []).push(cb);
}
emit<K extends keyof T>(event: K, payload: T[K]) {
this.handlers[event]?.forEach((cb) => cb(payload));
}
}
const bus = new TypedEmitter<EventMap>();
bus.on("login", (p) => {
// p 自动推导为 { userId: string; time: number }
console.log(p.userId, p.time);
});
bus.emit("purchase", { orderId: "o1", amount: 99 }); // OK
// bus.emit("purchase", { orderId: "o1" }); // Error: 缺少 amount类型编程的取舍
高级类型虽强大,但也要克制。过度复杂的类型体操会让代码可读性骤降、编译变慢。几个建议:
小结
TypeScript 高级类型编程的核心工具是:泛型(参数化)、keyof + 映射类型(批量变换)、条件类型 + infer(判断与捕获)、模板字面量(字符串拼接)以及递归(处理嵌套)。把它们组合起来,你就能构建出像 TypedEmitter 这样完全类型安全的基础设施。类型不是负担,而是契约——写得好,它就是不会撒谎的文档和测试。
💬 评论区 (0)
暂无评论,快来抢沙发吧!