TypeScript 泛型:从基础到进阶
这一篇深入 TypeScript 的泛型系统:从基础的泛型函数和接口,
到泛型约束、工具类型,再到条件类型和分布式条件类型。
泛型基础
泛型(Generics)允许你在定义函数、接口、类时不指定具体的类型,而是在使用时再指定。
为什么需要泛型?
1// 不使用泛型:需要为每种类型写一个函数
2function identityString(value: string): string {
3 return value;
4}
5
6function identityNumber(value: number): number {
7 return value;
8}
9
10// 使用 any:失去类型检查
11function identityAny(value: any): any {
12 return value;
13}
14
15// 使用泛型:一个函数处理多种类型,且保持类型安全
16function identity<T>(value: T): T {
17 return value;
18}
19
20const str = identity<string>('hello'); // str 的类型是 string
21const num = identity<number>(42); // num 的类型是 number
泛型函数
1// 基础语法
2function identity<T>(value: T): T {
3 return value;
4}
5
6// TypeScript 可以推断类型
7const str = identity('hello'); // 自动推断为 string
8const num = identity(42); // 自动推断为 number
9
10// 多个泛型参数
11function pair<T, U>(first: T, second: U): [T, U] {
12 return [first, second];
13}
14
15const result = pair<string, number>('hello', 42);
16// result 的类型是 [string, number]
泛型接口
1// 定义泛型接口
2interface Box<T> {
3 value: T;
4}
5
6const stringBox: Box<string> = { value: 'hello' };
7const numberBox: Box<number> = { value: 42 };
8
9// 泛型接口的方法
10interface Container<T> {
11 get(): T;
12 set(value: T): void;
13}
14
15class ArrayContainer<T> implements Container<T> {
16 private items: T[] = [];
17
18 get(): T {
19 return this.items[0];
20 }
21
22 set(value: T): void {
23 this.items.push(value);
24 }
25}
泛型类
1class Stack<T> {
2 private items: T[] = [];
3
4 push(item: T): void {
5 this.items.push(item);
6 }
7
8 pop(): T | undefined {
9 return this.items.pop();
10 }
11
12 peek(): T | undefined {
13 return this.items[this.items.length - 1];
14 }
15
16 isEmpty(): boolean {
17 return this.items.length === 0;
18 }
19}
20
21// 使用
22const numberStack = new Stack<number>();
23numberStack.push(1);
24numberStack.push(2);
25const num = numberStack.pop(); // num 的类型是 number | undefined
26
27const stringStack = new Stack<string>();
28stringStack.push('hello');
泛型约束
泛型约束允许你限制泛型参数必须满足某些条件。
extends 约束
1// 约束 T 必须有 length 属性
2function getLength<T extends { length: number }>(value: T): number {
3 return value.length;
4}
5
6getLength('hello'); // 正确:string 有 length
7getLength([1, 2, 3]); // 正确:array 有 length
8// getLength(42); // 错误:number 没有 length
9
10// 约束 T 必须是某个类型的子类型
11interface HasId {
12 id: string;
13}
14
15function getById<T extends HasId>(items: T[], id: string): T | undefined {
16 return items.find(item => item.id === id);
17}
18
19const users = [
20 { id: '1', name: 'John' },
21 { id: '2', name: 'Jane' }
22];
23
24const user = getById(users, '1'); // user 的类型是 { id: string; name: string } | undefined
keyof 约束
1// 约束 K 必须是 T 的键
2function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
3 return obj[key];
4}
5
6const person = {
7 name: 'John',
8 age: 30,
9 email: 'john@example.com'
10};
11
12const name = getProperty(person, 'name'); // name 的类型是 string
13const age = getProperty(person, 'age'); // age 的类型是 number
14// const invalid = getProperty(person, 'invalid'); // 错误:'invalid' 不是 person 的键
多个约束
1// T 必须同时满足多个约束
2interface HasId {
3 id: string;
4}
5
6interface HasName {
7 name: string;
8}
9
10function processItem<T extends HasId & HasName>(item: T): void {
11 console.log(item.id, item.name);
12}
13
14const item = {
15 id: '1',
16 name: 'John',
17 age: 30
18};
19
20processItem(item); // 正确:item 有 id 和 name
泛型工具类型
TypeScript 提供了一些内置的泛型工具类型,用于常见的类型转换。
Partial
将类型 T 的所有属性变为可选:
1interface User {
2 name: string;
3 age: number;
4 email: string;
5}
6
7type PartialUser = Partial<User>;
8// { name?: string; age?: number; email?: string; }
9
10// 实现原理
11type MyPartial<T> = {
12 [P in keyof T]?: T[P];
13};
Required
将类型 T 的所有属性变为必需:
1interface User {
2 name?: string;
3 age?: number;
4}
5
6type RequiredUser = Required<User>;
7// { name: string; age: number; }
8
9// 实现原理
10type MyRequired<T> = {
11 [P in keyof T]-?: T[P];
12};
Readonly
将类型 T 的所有属性变为只读:
1interface User {
2 name: string;
3 age: number;
4}
5
6type ReadonlyUser = Readonly<User>;
7// { readonly name: string; readonly age: number; }
8
9// 实现原理
10type MyReadonly<T> = {
11 readonly [P in keyof T]: T[P];
12};
Pick<T, K>
从类型 T 中选择指定的属性 K:
1interface User {
2 name: string;
3 age: number;
4 email: string;
5 password: string;
6}
7
8type PublicUser = Pick<User, 'name' | 'age' | 'email'>;
9// { name: string; age: number; email: string; }
10
11// 实现原理
12type MyPick<T, K extends keyof T> = {
13 [P in K]: T[P];
14};
Omit<T, K>
从类型 T 中排除指定的属性 K:
1interface User {
2 name: string;
3 age: number;
4 email: string;
5 password: string;
6}
7
8type PublicUser = Omit<User, 'password'>;
9// { name: string; age: number; email: string; }
10
11// 实现原理
12type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
Record<K, T>
创建一个对象类型,键的类型是 K,值的类型是 T:
1type UserRoles = 'admin' | 'user' | 'guest';
2type UserPermissions = string[];
3
4type RolePermissions = Record<UserRoles, UserPermissions>;
5// {
6// admin: string[];
7// user: string[];
8// guest: string[];
9// }
10
11// 实现原理
12type MyRecord<K extends keyof any, T> = {
13 [P in K]: T;
14};
Exclude<T, U>
从类型 T 中排除可以赋值给 U 的类型:
1type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
2// 'b' | 'c'
3
4type T1 = Exclude<string | number | boolean, number>;
5// string | boolean
6
7// 实现原理
8type MyExclude<T, U> = T extends U ? never : T;
Extract<T, U>
从类型 T 中提取可以赋值给 U 的类型:
1type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
2// 'a'
3
4type T1 = Extract<string | number | boolean, number>;
5// number
6
7// 实现原理
8type MyExtract<T, U> = T extends U ? T : never;
NonNullable
从类型 T 中排除 null 和 undefined:
1type T0 = NonNullable<string | number | undefined>;
2// string | number
3
4type T1 = NonNullable<string[] | null | undefined>;
5// string[]
6
7// 实现原理
8type MyNonNullable<T> = T extends null | undefined ? never : T;
Parameters
提取函数类型 T 的参数类型元组:
1type Func = (a: string, b: number) => boolean;
2type Args = Parameters<Func>;
3// [string, number]
4
5// 实现原理
6type MyParameters<T extends (...args: any) => any> =
7 T extends (...args: infer P) => any ? P : never;
ReturnType
提取函数类型 T 的返回类型:
1type Func = () => string;
2type Return = ReturnType<Func>;
3// string
4
5// 实现原理
6type MyReturnType<T extends (...args: any) => any> =
7 T extends (...args: any) => infer R ? R : any;
ConstructorParameters
提取构造函数类型 T 的参数类型元组:
1class User {
2 constructor(name: string, age: number) {}
3}
4
5type ConstructorArgs = ConstructorParameters<typeof User>;
6// [string, number]
InstanceType
提取构造函数类型 T 的实例类型:
1class User {
2 name: string;
3 age: number;
4}
5
6type UserInstance = InstanceType<typeof User>;
7// User
条件类型(Conditional Types)
条件类型根据类型关系测试来选择两种类型之一。
基础语法
1// T extends U ? X : Y
2// 如果 T 可以赋值给 U,则类型是 X,否则是 Y
3
4type IsString<T> = T extends string ? true : false;
5
6type A = IsString<string>; // true
7type B = IsString<number>; // false
条件类型的实际应用
1// 提取数组元素的类型
2type ArrayElementType<T> = T extends (infer U)[] ? U : never;
3
4type Element = ArrayElementType<string[]>; // string
5type Element2 = ArrayElementType<number[]>; // number
6
7// 提取 Promise 的类型
8type PromiseType<T> = T extends Promise<infer U> ? U : T;
9
10type Result = PromiseType<Promise<string>>; // string
11
12// 函数参数类型提取
13type FirstArg<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;
14
15type Arg = FirstArg<(a: string, b: number) => void>; // string
infer 关键字
infer 用于在条件类型中推断类型:
1// 推断数组元素类型
2type Flatten<T> = T extends (infer U)[] ? U : T;
3
4type Str = Flatten<string[]>; // string
5type Num = Flatten<number>; // number
6
7// 推断函数返回类型
8type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
9
10// 推断函数参数类型
11type Parameters<T> = T extends (...args: infer P) => any ? P : never;
分布式条件类型
当条件类型作用于联合类型时,会分布到每个成员:
1// 分布式条件类型
2type ToArray<T> = T extends any ? T[] : never;
3
4type StrArrOrNumArr = ToArray<string | number>;
5// string[] | number[] (不是 (string | number)[])
6
7// 非分布式条件类型
8type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
9
10type StrArrOrNumArr2 = ToArrayNonDist<string | number>;
11// (string | number)[]
实际应用:过滤类型
1// 从联合类型中过滤出可以赋值给 U 的类型
2type Filter<T, U> = T extends U ? T : never;
3
4type T0 = Filter<'a' | 'b' | 'c', 'a' | 'b'>;
5// 'a' | 'b'
6
7// 从联合类型中排除可以赋值给 U 的类型
8type Exclude<T, U> = T extends U ? never : T;
9
10type T1 = Exclude<'a' | 'b' | 'c', 'a'>;
11// 'b' | 'c'
实际应用:提取函数重载的返回类型
1type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
2
3// 处理函数重载
4type OverloadReturnType<T> = T extends {
5 (...args: any[]): infer R;
6 (...args: any[]): infer R;
7 (...args: any[]): infer R;
8} ? R : T extends (...args: any[]) => infer R ? R : any;
实际应用场景
API 响应类型
1// 定义通用的 API 响应类型
2type ApiResponse<T> =
3 | { success: true; data: T }
4 | { success: false; error: string };
5
6async function fetchUser(id: string): Promise<ApiResponse<User>> {
7 try {
8 const response = await fetch(`/api/users/${id}`);
9 const data = await response.json();
10 return { success: true, data };
11 } catch (error) {
12 return { success: false, error: error.message };
13 }
14}
15
16// 使用
17const result = await fetchUser('123');
18if (result.success) {
19 console.log(result.data); // TypeScript 知道这里有 data
20} else {
21 console.error(result.error); // TypeScript 知道这里有 error
22}
深度只读类型
1type DeepReadonly<T> = {
2 readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
3};
4
5interface User {
6 name: string;
7 address: {
8 street: string;
9 city: string;
10 };
11}
12
13type ReadonlyUser = DeepReadonly<User>;
14// {
15// readonly name: string;
16// readonly address: {
17// readonly street: string;
18// readonly city: string;
19// };
20// }
深度可选类型
1type DeepPartial<T> = {
2 [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
3};
4
5interface User {
6 name: string;
7 address: {
8 street: string;
9 city: string;
10 };
11}
12
13type PartialUser = DeepPartial<User>;
14// {
15// name?: string;
16// address?: {
17// street?: string;
18// city?: string;
19// };
20// }
函数重载类型
1// 提取所有重载的返回类型
2type OverloadReturnType<T> = T extends {
3 (...args: any[]): infer R1;
4 (...args: any[]): infer R2;
5 (...args: any[]): infer R3;
6 (...args: any[]): infer R4;
7 (...args: any[]): infer R5;
8} ? R1 | R2 | R3 | R4 | R5 : T extends (...args: any[]) => infer R ? R : any;
类型安全的 EventEmitter
1type EventMap = {
2 click: { x: number; y: number };
3 change: string;
4 error: Error;
5};
6
7class TypedEventEmitter<T extends Record<string, any>> {
8 private listeners: {
9 [K in keyof T]?: Array<(data: T[K]) => void>;
10 } = {};
11
12 on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
13 if (!this.listeners[event]) {
14 this.listeners[event] = [];
15 }
16 this.listeners[event]!.push(listener);
17 }
18
19 emit<K extends keyof T>(event: K, data: T[K]): void {
20 const listeners = this.listeners[event];
21 if (listeners) {
22 listeners.forEach(listener => listener(data));
23 }
24 }
25}
26
27// 使用
28const emitter = new TypedEventEmitter<EventMap>();
29emitter.on('click', (data) => {
30 console.log(data.x, data.y); // TypeScript 知道 data 有 x 和 y
31});
32
33emitter.on('change', (data) => {
34 console.log(data.toUpperCase()); // TypeScript 知道 data 是 string
35});
36
37emitter.emit('click', { x: 10, y: 20 }); // 类型安全
小结
TypeScript 的泛型系统提供了强大的类型抽象能力:
- 泛型基础:泛型函数、接口、类,让代码更通用且类型安全
- 泛型约束:
extends和keyof约束,限制泛型参数的范围 - 工具类型:
Partial、Required、Pick、Omit、Record等,简化常见类型转换 - 条件类型:根据类型关系选择类型,实现复杂的类型逻辑
- infer 关键字:在条件类型中推断类型
- 分布式条件类型:处理联合类型时的分布行为
掌握泛型系统,可以写出更灵活、更类型安全的 TypeScript 代码,
让类型系统成为开发时的有力助手,而不是负担。