TypeScript中的泛型是什么?

0 阅读4分钟

TypeScript 中的泛型(Generics) 是类型系统中非常强大且核心的功能。简单来说,泛型允许你编写 “可复用” 的代码组件(如函数、类、接口),这些组件可以处理多种数据类型,同时还能保持类型安全

你可以把泛型理解为: “类型的参数化” 。就像函数接收参数一样,泛型让类型本身也能接收参数。

1. 为什么要用泛型?(解决什么问题)

如果没有泛型,当你想要编写一个“原样返回输入值”的函数时,会面临两难选择:

❌ 方案 A:使用 any(丢失类型检查)

function identity(arg: any): any {
    return arg;
}
const result = identity("Hello");
// result 的类型是 any,编辑器无法提示字符串方法,失去了 TS 的意义 result.toFixed(); // ❌ 运行时才会报错,TS 编译时不报错

❌ 方案 B:写多个重载函数(代码冗余)

function identityString(arg: string): string { return arg; }
function identityNumber(arg: number): number { return arg; }
// 如果有 100 种类型,就要写 100 个函数...

✅ 方案 C:使用泛型(完美方案)

// <T> 声明了一个类型变量 T
function identity<T>(arg: T): T {
  return arg;
}

// 调用时明确指定 T 为 string
const str = identity<string>("Hello"); 
// str 的类型自动推断为 string,拥有完整的智能提示
str.toUpperCase(); // ✅ OK

// 调用时明确指定 T 为 number
const num = identity<number>(123);
// num 的类型自动推断为 number
num.toFixed(); // ✅ OK

// TypeScript 通常能自动推断,甚至可以省略 <string>
const auto = identity("Auto Infer"); // T 自动被推断为 string

2. 泛型的核心语法

A. 基础用法:类型变量 T

T 是 convention(惯例),代表 Type。你也可以用 UVKV (Key, Value) 等,只要成对出现即可。

// 定义一个泛型函数
function createPair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 使用
const pair1 = createPair<string, number>("age", 18); 
// 类型: [string, number]

const pair2 = createPair(true, "hello"); 
// 类型自动推断: [boolean, string]

B. 泛型接口 (Interfaces)

接口也可以定义泛型,常用于定义数据结构(如 API 响应、列表项)。

// 定义一个通用的盒子结构
interface Box<T> {
  content: T;
}

const stringBox: Box<string> = { content: "Hello" };
const numberBox: Box<number> = { content: 100 };

// 泛型接口还可以有多个参数
interface Map<K, V> {
  key: K;
  value: V;
}

const userMap: Map<string, { id: number; name: string }> = {
  key: "user_1",
  value: { id: 1, name: "Alice" }
};

C. 泛型类 (Classes)

类也可以使用泛型,确保类内部的方法和属性类型一致。

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
// numberStack.push("error"); // ❌ 报错:不能推入字符串

const top = numberStack.pop(); // top 的类型是 number | undefined

3. 高级泛型技巧

A. 泛型约束 (Constraints) - extends

有时候我们希望泛型 T 必须是某种特定的类型(比如必须包含 length 属性),这时可以使用 extends 关键字。

// 错误示范:T 可能是 number,number 没有 .length 属性
// function loggingIdentity<T>(arg: T): T {
//   console.log(arg.length); // ❌ 报错
//   return arg;
// }

// 正确示范:约束 T 必须具有 length 属性
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // ✅ 现在 TS 知道 arg 一定有 length
  return arg;
}

loggingIdentity("hello"); // ✅ string 有 length
loggingIdentity([1, 2, 3]); // ✅ array 有 length
loggingIdentity({ length: 10, width: 5 }); // ✅ 对象有 length

// loggingIdentity(123); // ❌ 报错:number 没有 length 属性

B. 在泛型约束中使用类型参数

你可以用一个类型参数去约束另一个类型参数。

// K 必须是 obj 的键名之一 (keyof T)
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const user = { id: 1, name: "Bob", active: true };

const id = getProperty(user, "id");     // ✅ 类型: number
const name = getProperty(user, "name"); // ✅ 类型: string

// const email = getProperty(user, "email"); // ❌ 报错:"email" 不是 user 的键

C. 默认泛型类型

可以为泛型指定默认值,如果调用时没传,就用默认值。

interface Response<T = string> {
  data: T;
  status: number;
}

// 不传泛型参数,T 默认为 string
const res1: Response = { data: "ok", status: 200 }; 

// 传入泛型参数,覆盖默认值
const res2: Response<{ id: number }> = { data: { id: 1 }, status: 200 };

4. 常见内置泛型工具类型

TypeScript 内置了许多实用的泛型工具类型,日常开发非常常用:

工具类型作用示例
Partial<T>将 T 的所有属性变为可选Partial<User>
Required<T>将 T 的所有属性变为必填Required<User>
Readonly<T>将 T 的所有属性变为只读Readonly<User>
Pick<T, K>从 T 中选取一组属性 KPick<User, 'id' | 'name>
Omit<T, K>从 T 中剔除一组属性 KOmit<User, "password">
Record<K, T>构造一个键类型为 K,值为 T 的对象Record<string, number>
ReturnType<T>获取函数 T 的返回值类型ReturnType<typeof myFunc>
Promise<T>表示一个解析为 T 的 PromisePromise<string>
interface User {
  id: number;
  name: string;
  email: string;
}

// 更新用户时,ID 和名字通常不变,只需要部分字段
type UpdateUserDto = Partial<Omit<User, 'id'>>; 
// 等价于: { name?: string; email?: string; }