1. 什么是泛型?为什么需要它?

泛型(Generics)是TypeScript中的一种特殊语法,允许我们创建可重用的组件,这些组件可以支持多种类型,而不是单一类型。

🤔 打个比方:

想象你有一个可以装任何东西的”魔法盒子”。泛型就是这个”魔法盒子”的设计图,它允许你创建可以容纳任何类型(字符串、数字、自定义对象等)的容器。

💡 为什么需要泛型?

在没有泛型的情况下,我们要么为每种类型写重复代码,要么使用宽松的any类型(失去类型安全性)。泛型解决了这个问题,让我们写出灵活且类型安全的代码。

问题示例:没有泛型
// 只能处理数字的函数
function identityNumber(arg: number): number {
  return arg;
}

// 只能处理字符串的函数
function identityString(arg: string): string {
  return arg;
}

// 使用any失去类型安全
function identityAny(arg: any): any {
  return arg; // 这里返回什么类型都不安全
}

2. 泛型函数:可复用的函数

泛型函数是最常用的泛型形式,它允许函数支持多种类型。

// 基本泛型函数语法
function identity<T>(arg: T): T {
  return arg;
}

// 使用方式:
let output1 = identity<string>(“hello”); // T是string
let output2 = identity(42); // 类型推导:T是number

🍪 通俗理解:

把泛型函数想象成饼干模具。模具(函数)是固定的,但你可以用它制作不同形状的饼干(不同类型的结果)。

实用示例:数组处理函数
// 获取数组第一个元素
function getFirstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

// 使用
const firstNum = getFirstElement([1, 2, 3]); // number | undefined
const firstStr = getFirstElement([“a”, “b”, “c”]); // string | undefined

3. 泛型接口:灵活的接口

泛型接口允许我们定义灵活的接口,能够适应多种类型的数据结构。

// 基本泛型接口
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

// 使用
let pair1: KeyValuePair<number, string> = { key: 1, value: “one” };
let pair2: KeyValuePair<string, boolean> = { key: “isValid”, value: true };

📦 通俗理解:

想象一个标签系统,标签(接口)定义了一个物品需要有哪些信息(属性),但具体每个位置放什么类型的内容,可以根据实际情况决定。

实用示例:API响应结构
interface ApiResponse<T> {
  success: boolean;
  message: string;
  data: T; // 实际数据部分,类型由使用者决定
}

// 用户接口
interface User {
  id: number;
  name: string;
}

// 使用
const userResponse: ApiResponse<User> = {
  success: true,
  message: “用户数据加载成功”,
  data: { id: 1, name: “张三” }
};

4. 泛型类:通用组件

泛型类允许我们创建可以处理多种数据类型的类。

// 基本泛型类:简单的栈
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);
const num = numberStack.pop(); // number | undefined

const stringStack = new Stack<string>();
stringStack.push(“hello”);
stringStack.push(“world”);
const str = stringStack.pop(); // string | undefined

🏭 通俗理解:

想象一个工厂(类),它有一条生产线(方法)。通过泛型,你可以轻松切换原材料(输入类型)和产品(输出类型),而不需要重建整个工厂。

5. 泛型约束:给泛型加限制

有时我们需要限制泛型类型的范围,确保它们具有某些属性或能力。

// 使用extends关键字约束泛型
interface HasLength {
  length: number;
}

// T必须满足HasLength接口(即有length属性)
function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length);
}

// 正确使用
logLength(“hello”); // string有length属性
logLength([1, 2, 3]); // 数组有length属性

// 错误使用
logLength(42); // 编译错误: number没有length属性

🚦 通俗理解:

就像交通规则限制车辆类型(如”仅允许长度小于6米的车辆通行”),泛型约束确保只有符合特定条件的类型才能使用。

实用示例:访问对象属性
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let person = { name: “张三”, age: 30 };
let name = getProperty(person, “name”); // string
let age = getProperty(person, “age”); // number

// 错误示例:
// getProperty(person, “email”); // 编译错误: “email”不是person的属性

6. 默认泛型类型:简化使用

你可以为泛型类型提供默认值,简化使用。

// 接口带默认泛型类型
interface Pagination<T = any> {
  currentPage: number;
  pageSize: number;
  total: number;
  data: T[];
}

// 使用默认类型
const response: Pagination = {
  currentPage: 1,
  pageSize: 10,
  total: 100,
  data: [{ id: 1 }, { id: 2 }] // data类型为any[]
};

// 指定具体类型
const userPagination: Pagination<User> = {
  currentPage: 1,
  pageSize: 10,
  total: 50,
  data: [{ id: 1, name: “张三” }] // data类型为User[]
};

7. 类型推导:TS的智能之处

TypeScript能够根据上下文自动推导泛型类型,减少冗余代码。

function createPair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 不需要显式指定类型
const pair1 = createPair(1, “one”); // [number, string]
const pair2 = createPair(true, { name: “test” }); // [boolean, { name: string }]

✅ 最佳实践:

在多数情况下,让TypeScript自动推导泛型类型,这样代码更简洁。仅在类型推导失败或需要明确指定时,才显式声明泛型类型。

8. 常见应用场景

  • 集合类:如数组、栈、队列、字典等
  • API响应处理:统一响应结构,不同数据类型
  • 工具函数:如映射、过滤、归约等数组操作
  • React组件:Props和State的类型定义
  • 状态管理:如Redux的action和reducer
  • 函数式编程:高阶函数、柯里化等

9. 泛型最佳实践

  • ➡️ 优先使用单字母泛型参数(T, U, V)
  • ➡️ 当含义明确时,使用更有意义的名称(如Key, Value)
  • ➡️ 尽量保持泛型简单
  • ➡️ 必要时使用约束(extends)
  • ➡️ 避免过度使用泛型,只在真正需要灵活性时使用
  • ➡️ 编写文档说明复杂泛型的用法

🌟 泛型学习总结

泛型是TypeScript中强大的工具,它能让你:

1️⃣ 编写更通用、可复用的代码
2️⃣ 保持类型安全,避免使用any
3️⃣ 提高代码的灵活性和可维护性

作为初学者,从简单的泛型函数开始练习,逐渐应用到接口和类中。记住:实践是最好的老师!