TypeScript泛型核心机制
TypeScript的类型系统图灵完备,泛型是构建可复用类型逻辑的基石。泛型允许在定义函数、接口、类型别名时留出类型参数位,在实际使用时填入具体类型。这和函数参数留出值位、调用时填入具体值是同一个思路,区别在于泛型参数在类型层面运作,编译后完全擦除。
// 基础泛型函数:类型参数T在调用时确定
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // T推导为number
const str = identity("hello"); // T推导为string
const explicit = identity<boolean>(true); // 显式指定
泛型约束(Constraint)限制类型参数必须满足的结构:
// T必须具有length属性
function logLength<T extends { length: number }>(value: T): number {
return value.length;
}
logLength("abc"); // OK, string有length
logLength([1, 2, 3]); // OK, Array有length
logLength(123); // Error: number没有length
// keyof约束:K必须是T的属性键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // 推导为string
const age = getProperty(user, "age"); // 推导为number
条件类型与类型推断
条件类型是TypeScript类型体操的核心工具,语法 T extends U ? X : Y 在类型层面做条件判断:
// 实现ReturnType:提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type Result = MyReturnType<Fn>; // string
// 实现Parameters:提取函数参数类型为元组
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type Params = MyParameters<(a: string, b: number) => void>; // [string, number]
// infer关键字在条件类型的extends子句中声明待推断的类型变量
// 只能在条件类型的真分支中访问infer推断的结果
分布式条件类型:当类型参数是联合类型时,条件类型会自动分发,逐一判断:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// 不是 (string | number)[]
// 阻止分发:用元组包裹
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNoDistribute<string | number>; // (string | number)[]
内置工具类型 Exclude、Extract、NonNullable 都基于分布式条件类型实现:
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;
type T1 = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = MyExtract<string | number | boolean, number>; // number
type T3 = MyNonNullable<string | null | undefined>; // string
映射类型与模板字面量类型
映射类型(Mapped Types)遍历联合类型中的每个键,生成新的对象类型:
// 实现Partial:将所有属性变为可选
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 实现Required:将所有属性变为必选
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // -?移除可选修饰符
};
// 实现Readonly:将所有属性变为只读
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// 实现Mutable:移除readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
模板字面量类型(Template Literal Types)在类型层面拼接字符串,配合映射类型可以生成大量类型安全的代码:
// 事件监听器的类型安全
type EventName = "click" | "focus" | "blur";
type EventHandler<T extends EventName> = `on${Capitalize<T>}`;
// "onClick" | "onFocus" | "onBlur"
// CSS属性的类型安全
type CSSProperty = "margin" | "padding" | "border";
type CSSDirection = "top" | "right" | "bottom" | "left";
type CSSRule = `${CSSProperty}-${CSSDirection}`;
// "margin-top" | "margin-right" | ... | "border-left"
// 实现类型安全的事件系统
type EventMap = {
click: { x: number; y: number };
keypress: { key: string; code: string };
resize: { width: number; height: number };
};
function on<K extends keyof EventMap>(
event: K,
handler: (payload: EventMap[K]) => void
): void {
// 实现
}
on("click", (payload) => {
payload.x; // 类型推导为number
payload.y; // 类型推导为number
});
高级类型模式实战
递归类型:实现深层Readonly:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K] // 函数不递归
: DeepReadonly<T[K]> // 对象递归
: T[K]; // 基本类型直接返回
};
interface Config {
db: { host: string; port: number; options: { ssl: boolean } };
cache: { ttl: number };
}
type FrozenConfig = DeepReadonly<Config>;
// db.port和db.options.ssl都变为readonly
类型推导与函数重载的结合:实现类型安全的事件发射器:
type EventPayloads = {
userLogin: { userId: string; timestamp: number };
pageView: { url: string; referrer: string };
error: { message: string; stack?: string };
};
class TypedEventEmitter<Events extends Record<string, any>> {
private listeners = new Map<keyof Events, Set<Function>>();
on<K extends keyof Events>(
event: K,
handler: (payload: Events[K]) => void
): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(handler);
// 返回取消订阅函数
return () => this.listeners.get(event)?.delete(handler);
}
emit<K extends keyof Events>(event: K, payload: Events[K]): void {
this.listeners.get(event)?.forEach(fn => fn(payload));
}
}
const emitter = new TypedEventEmitter<EventPayloads>();
emitter.on("userLogin", (p) => console.log(p.userId)); // p推导正确
emitter.emit("pageView", { url: "/", referrer: "" }); // payload类型检查
emitter.emit("error", { message: "fail" }); // stack可选
类型体操经典题解析
实现DeepPartial:递归将所有嵌套属性变为可选:
type DeepPartial<T> = T extends Function
? T
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// 验证
type Nested = { a: { b: { c: string } }; d: number };
type PartialNested = DeepPartial<Nested>;
// { a?: { b?: { c?: string } }; d?: number }
实现PickByType:按值类型筛选属性:
type PickByType<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K]
};
interface Data {
name: string;
age: number;
active: boolean;
score: number;
}
type NumberFields = PickByType<Data, number>;
// { age: number; score: number }
type StringFields = PickByType<Data, string>;
// { name: string }
实现TupleToUnion:元组转联合类型:
type TupleToUnion<T extends readonly unknown[]> = T[number];
type Colors = readonly ["red", "green", "blue"];
type Color = TupleToUnion<Colors>; // "red" | "green" | "blue"
TypeScript泛型的能力远超”给函数加类型参数”的范畴。条件类型、映射类型、模板字面量类型、infer推断组合起来,可以在类型层面实现复杂的逻辑运算,在编译期捕获大量运行时错误。类型体操的目标不是写出最复杂的类型表达式,而是用最精确的类型约束来描述业务逻辑,让编译器成为最可靠的代码审查工具。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-gao-ji-zhi-nan-tiao-jian-lei-xing-ying/