泛型约束的正确打开方式
TypeScript泛型的威力不仅在于参数化类型,更在于通过extends关键字约束泛型的形状。约束粒度决定了类型安全的强度。看一个渐进式约束的例子:
// 最宽松:无约束,T可以是任何类型
function identity<T>(arg: T): T {
return arg;
}
// 中等约束:要求T有特定属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
// 严格约束:结合keyof和泛型,实现类型安全的属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30, active: true };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
const err = getProperty(user, "email"); // 编译错误:email不是user的key
条件类型与分布式条件类型
条件类型(Conditional Types)是TypeScript类型系统的图灵完备机制。基本语法T extends U ? X : Y,当T传入泛型参数且为联合类型时,会自动分布计算:
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 分布式行为:对联合类型的每个成员分别计算
type IsStringDist<T> = T extends string ? true : false;
type C = IsStringDist<string | number>; // true | false → boolean
// 阻止分布式行为:用[]包裹T
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type D = ToArrayNonDist<string | number>; // (string | number)[] 而非 string[] | number[]
分布式行为在多数场景下是期望的(如对每个联合成员做映射),但在某些场景下需要阻止。理解两者的区别是写好类型工具函数的前提。
infer关键字:类型推断的模式匹配
infer用于在条件类型中推断子类型,类似正则表达式的捕获组:
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<(x: number) => void>; // void
// 提取Promise内部的值类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type P1 = Awaited<Promise<string>>; // string
type P2 = Awaited<Promise<Promise<number>>>; // number(递归解包)
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type E1 = ElementOf<string[]>; // string
type E2 = ElementOf<number[]>; // number
// 提取构造函数参数类型
type ConstructorParams<T> = T extends new (...args: infer P) => any ? P : never;
class Foo {
constructor(public x: number, public y: string) {}
}
type FooParams = ConstructorParams<typeof Foo>; // [x: number, y: string]
模板字面量类型:字符串级别的类型操作
TypeScript 4.1引入的模板字面量类型,让类型系统可以操作字符串:
// 事件监听器的类型安全
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
// CSS属性类型构建
type CSSUnit = "px" | "rem" | "em" | "%" | "vh" | "vw";
type CSSValue<T extends number | string> = T extends number
? `${T}${CSSUnit}`
: T;
// API路由的类型推导
type APIRoute<Version extends "v1" | "v2", Resource extends string> =
`/api/${Version}/${Resource}`;
type UserV1 = APIRoute<"v1", "users">; // "/api/v1/users"
type PostV2 = APIRoute<"v2", "posts">; // "/api/v2/posts"
实战:构建类型安全的EventEmitter
将上述技术组合起来,构建一个完全类型安全的事件系统:
interface EventMap {
login: { userId: string; timestamp: number };
logout: { userId: string };
error: { code: number; message: string };
data: { payload: unknown };
}
type EventHandler<T extends keyof EventMap> = (payload: EventMap[T]) => void;
class TypeSafeEmitter {
private handlers = new Map<string, Set<Function>>();
on<T extends keyof EventMap>(event: T, handler: EventHandler<T>): void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
}
emit<T extends keyof EventMap>(event: T, payload: EventMap[T]): void {
const handlers = this.handlers.get(event);
if (handlers) {
handlers.forEach(fn => fn(payload));
}
}
off<T extends keyof EventMap>(event: T, handler: EventHandler<T>): void {
this.handlers.get(event)?.delete(handler);
}
}
// 使用:完全类型安全
const emitter = new TypeSafeEmitter();
emitter.on("login", (p) => {
console.log(p.userId); // OK: userId是string
console.log(p.timestamp); // OK: timestamp是number
});
emitter.emit("login", { userId: "u1", timestamp: Date.now() }); // OK
emitter.emit("login", { userId: "u1" }); // 编译错误:缺少timestamp
emitter.emit("error", { userId: "u1" }); // 编译错误:payload类型不匹配
类型推导的性能边界
复杂类型推导会增加编译时间。当项目中的类型嵌套超过10层或联合类型成员超过50个时,tsc可能明显变慢。优化方式:用type别名命名中间类型避免重复计算、减少不必要的递归深度、对大型联合类型使用预处理脚本生成类型定义文件而非让tsc每次推导。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-tui-dao-shi-zhan-cong-fan-xing/