TypeScript泛型

67 阅读5分钟

一、前言

在 TypeScript 开发中,我们经常会遇到需要编写可复用、灵活且类型安全的函数或组件的情况。传统的做法是使用 any 类型来处理未知类型,但这会牺牲类型检查的优势。

TypeScript 的泛型(Generics) 正是为了解决这个问题而设计的——它允许我们在定义函数、接口或类时,不预先指定具体类型,而在使用时再指定类型,从而实现类型参数化和类型安全的复用。

本文将带你全面了解 TypeScript 中的泛型机制,包括:

✅ 泛型函数的定义与调用
✅ 泛型接口与类的使用
✅ 泛型约束与默认类型
✅ 常见泛型工具类型(Partial、Required、Record 等)
✅ 实际开发中的典型应用案例

并通过大量代码示例帮助你深入理解泛型的精髓。

二、什么是泛型?

✅ 定义:

泛型是一种编程范式,它允许我们在编写函数、接口或类时使用类型变量(type variable),这些变量会在实际调用时被具体的类型替换。

⚠️ 核心优势:

  • 提高代码复用性
  • 保持类型安全性
  • 避免使用 any 导致的类型丢失
  • 支持多种数据类型的统一处理

三、泛型函数(Generic Functions)

✅ 示例1:基本泛型函数

function identity<T>(arg: T): T {
  return arg;
}

const output1 = identity<string>("Hello"); // string 类型
const output2 = identity<number>(42);     // number 类型

✅ 示例2:类型推断调用泛型函数

const output3 = identity(true); // 自动推断为 boolean 类型

四、泛型接口(Generic Interfaces)

✅ 示例:定义一个泛型接口

interface Box<T> {
  value: T;
}

const box1: Box<string> = { value: "Apple" };
const box2: Box<number> = { value: 123 };

五、泛型类(Generic 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(10);
console.log(numberStack.pop()); // 10

const stringStack = new Stack<string>();
stringStack.push("Hello");
console.log(stringStack.pop()); // Hello

六、泛型约束(Generic Constraints)

有时我们需要限制泛型的类型范围,例如只能是某个接口的子类型。

✅ 示例:使用 extends 添加泛型约束

interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello");       // 字符串有 length 属性 ✅
logLength([1, 2, 3]);     // 数组也有 length 属性 ✅
// logLength(42);         // ❌ 报错:number 没有 length 属性

七、多个类型参数

你可以定义多个泛型参数,用于更复杂的逻辑处理。

✅ 示例:多泛型参数函数

function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

const result = pair<string, number>("age", 25); // ["age", 25]

八、泛型工具类型(Utility Types)

TypeScript 内置了许多常用的泛型工具类型,极大提升了开发效率。

✅ 1. Partial<T> —— 将所有属性变为可选

interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = Partial<User>;
// 相当于:
// {
//   id?: number;
//   name?: string;
//   email?: string;
// }

const user: PartialUser = { name: "Alice" }; // 合法

✅ 2. Required<T> —— 所有属性必须存在

type RequiredUser = Required<PartialUser>;
// 所有字段都变回必填

✅ 3. Readonly<T> —— 所有属性只读

type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: "Alice", email: "a@example.com" };
// user.id = 2; ❌ 错误:属性是只读的

✅ 4. Record<K, T> —— 创建键值对对象

type StatusMap = Record<string, number>;
const status: StatusMap = {
  loading: 0,
  success: 1,
  error: 2
};

✅ 5. Pick<T, K> —— 从类型 T 中选取部分属性

type UserNameAndEmail = Pick<User, 'name' | 'email'>;
const info: UserNameAndEmail = {
  name: "Bob",
  email: "bob@example.com"
};

✅ 6. Omit<T, K> —— 排除某些属性

type UserWithoutId = Omit<User, 'id'>;
const user: UserWithoutId = {
  name: "Charlie",
  email: "charlie@example.com"
};

九、泛型默认类型(Default Generic Types)

你可以为泛型提供默认类型,提高灵活性。

✅ 示例:设置泛型默认值

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

const res1: Response = { data: "OK", status: 200 }; // 默认 string
const res2: Response<number> = { data: 123, status: 200 };

十、实际开发中的泛型应用场景

✅ 场景1:通用 API 请求封装

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return await response.json();
}

interface User {
  id: number;
  name: string;
}

const user = await fetchData<User>("/api/user/1");
console.log(user.name);

✅ 场景2:React 组件 props 类型抽象

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => JSX.Element;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 使用:
<List<string>
  items={["Apple", "Banana"]}
  renderItem={(item) => <span>{item}</span>}
/>

十一、注意事项与最佳实践

场景建议
是否推荐使用泛型代替 any✅ 是,避免类型丢失
是否建议泛型命名清晰✅ 是,如 TUKV 等标准命名
是否应避免过度泛型化✅ 是,仅在需要复用时使用
是否推荐结合接口约束使用✅ 是,增强类型安全性
是否支持泛型重载?✅ 是,但需谨慎使用

十二、总结对比表:TypeScript 泛型常用特性一览

特性示例说明
泛型函数function identity<T>(arg: T)类型由调用决定
泛型接口interface Box<T>描述泛型结构
泛型类class Stack<T>可复用的数据结构
泛型约束T extends Lengthwise限制类型范围
多个泛型参数<K, V>更复杂逻辑支持
默认泛型类型T = string增强灵活性
工具类型Partial<T>, Readonly<T>快速构建新类型
实际用途数据结构、API、组件等构建可维护项目

十三、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!