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');

泛型约束允许你限制泛型参数必须满足某些条件。

 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
 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 提供了一些内置的泛型工具类型,用于常见的类型转换。

将类型 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};

将类型 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};

将类型 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};

从类型 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};

从类型 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>>;

创建一个对象类型,键的类型是 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};

从类型 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;

从类型 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;

从类型 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;

提取函数类型 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;

提取函数类型 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;

提取构造函数类型 T 的参数类型元组:

1class User {
2  constructor(name: string, age: number) {}
3}
4
5type ConstructorArgs = ConstructorParameters<typeof User>;
6// [string, number]

提取构造函数类型 T 的实例类型:

1class User {
2  name: string;
3  age: number;
4}
5
6type UserInstance = InstanceType<typeof User>;
7// User

条件类型根据类型关系测试来选择两种类型之一。

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 用于在条件类型中推断类型:

 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;
 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;
 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 的泛型系统提供了强大的类型抽象能力:

  • 泛型基础:泛型函数、接口、类,让代码更通用且类型安全
  • 泛型约束extendskeyof 约束,限制泛型参数的范围
  • 工具类型PartialRequiredPickOmitRecord 等,简化常见类型转换
  • 条件类型:根据类型关系选择类型,实现复杂的类型逻辑
  • infer 关键字:在条件类型中推断类型
  • 分布式条件类型:处理联合类型时的分布行为

掌握泛型系统,可以写出更灵活、更类型安全的 TypeScript 代码,
让类型系统成为开发时的有力助手,而不是负担。