TypeScript 泛型(实战指南)

TypeScript 泛型:让代码更灵活、更安全

在开发中,我们常常会遇到这样的情景:写一个函数,它能处理多种类型的数据,比如数组、字符串、数字,甚至自定义对象。如果用普通类型声明,就必须为每种类型写一遍逻辑,这不仅重复,还容易出错。TypeScript 泛型正是为了解决这类“类型复用”问题而生的。

你可以把泛型想象成一个“模板”,它不指定具体类型,而是用一个占位符(比如 T)来代表未来可能传入的类型。这样,同一个函数或类,就能在不修改代码的前提下,安全地处理不同类型的值。

这篇文章会带你一步步理解 TypeScript 泛型的核心概念,从基础语法到高级用法,通过真实案例帮你掌握它的实用技巧。


什么是 TypeScript 泛型?简单说就是“类型参数”

TypeScript 泛型允许我们在定义函数、接口、类时,不指定具体的类型,而是用一个类型参数来占位。这个参数在实际使用时会被替换为真实的类型。

举个例子,我们想写一个函数,它能返回传入的任意值。如果不用泛型,只能写成:

function identity(arg: any): any {
  return arg;
}

这个写法虽然能工作,但失去了类型推断的能力。比如:

const result = identity("Hello");
console.log(result.toUpperCase()); // 没有类型提示!

因为返回值是 any,TypeScript 不知道 result 是字符串,所以无法提供智能提示。

而使用泛型后,代码就变得既灵活又安全:

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

// 使用时指定类型
const strResult = identity<string>("Hello");
const numResult = identity<number>(123);

console.log(strResult.toUpperCase()); // ✅ 有类型提示
console.log(numResult.toFixed(2));    // ✅ 有类型提示

注释:<T> 表示这是一个泛型参数,T 可以是任意名称,常见用 TKV 等。identity<T> 表示这个函数接受一个类型为 T 的参数,也返回 T 类型的值。


泛型函数:让函数支持多种类型

泛型函数的核心思想是“类型延迟绑定”——类型在调用时才确定。这使得我们能写出更通用、可复用的函数。

创建一个通用的打印函数

假设我们想写一个函数,用来打印数组内容,但不关心数组里是什么类型:

function printArray<T>(arr: T[]): void {
  arr.forEach(item => {
    console.log(item);
  });
}

// 使用示例
printArray<string>(["a", "b", "c"]);
printArray<number>([1, 2, 3]);
printArray<boolean>([true, false, true]);

注释:T[] 表示“任意类型 T 的数组”。printArray<T> 的泛型参数 T 被用来约束数组元素的类型。调用时传入 <string>,TypeScript 就知道数组里是字符串。

类型推断让调用更简洁

TypeScript 支持类型推断,如果传入的参数类型明确,就不需要手动写泛型参数:

printArray(["x", "y", "z"]); // ✅ 自动推断为 string[]
printArray([10, 20, 30]);    // ✅ 自动推断为 number[]

这大大减少了代码冗余,让代码更简洁。


泛型接口:定义灵活的结构

接口是 TypeScript 中定义对象结构的重要工具。当接口需要支持多种类型时,泛型就派上用场了。

定义一个通用的响应接口

在前后端交互中,我们常看到类似结构:

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T; // 数据部分类型不确定
}

注释:<T> 表示这个接口的 data 字段可以是任意类型。这样,无论返回的是用户列表、订单详情,还是单个对象,都能安全使用。

使用示例:

const userResponse: ApiResponse<User> = {
  code: 200,
  message: "success",
  data: { id: 1, name: "Alice" }
};

const listResponse: ApiResponse<User[]> = {
  code: 200,
  message: "success",
  data: [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" }
  ]
};

注释:ApiResponse<User> 表示 dataUser 类型;ApiResponse<User[]> 表示 dataUser 数组。TypeScript 会严格检查类型,避免错误。


泛型类:让类支持多种数据类型

类是面向对象编程的核心。当一个类需要处理多种数据类型时,泛型可以让类更加通用。

实现一个通用的栈(Stack)结构

栈是一种后进先出(LIFO)的数据结构。我们可以用泛型实现一个支持任意类型的栈:

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

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

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

  // 查看栈顶元素
  peek(): T | undefined {
    return this.items.length > 0 ? this.items[this.items.length - 1] : undefined;
  }

  // 检查是否为空
  isEmpty(): boolean {
    return this.items.length === 0;
  }

  // 获取大小
  size(): number {
    return this.items.length;
  }
}

// 使用示例
const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.pop()); // "b"

const numberStack = new Stack<number>();
numberStack.push(100);
numberStack.push(200);
console.log(numberStack.peek()); // 200

注释:Stack<T> 中的 T 代表栈中元素的类型。创建实例时传入 stringnumber,TypeScript 会自动绑定类型,确保操作安全。


泛型约束:限制可接受的类型

有时候我们希望泛型只能接受特定类型的值。比如,我们想让泛型函数只能处理有 .length 属性的类型(如字符串、数组)。

这时可以使用“泛型约束”:

function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

// 使用示例
console.log(getLength("hello"));     // ✅ 3
console.log(getLength([1, 2, 3]));   // ✅ 3
console.log(getLength(123));         // ❌ 编译错误!number 没有 length 属性

注释:T extends { length: number } 表示 T 必须是一个拥有 length 属性且类型为 number 的对象。这限制了泛型只能用于支持 length 的类型。

常见约束类型

类型约束 说明
T extends string 只能是字符串类型
T extends number 只能是数字类型
T extends object 只能是对象类型
T extends { name: string } 必须有 name 字段且为字符串

泛型高级技巧:联合类型与映射

使用泛型处理对象属性

有时我们需要从一个对象中提取某些字段,比如从 User 类型中提取 nameage

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

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

// 提取 name 和 age
type UserBasic = Pick<User, "name" | "age">;

const userBasic: UserBasic = {
  name: "Alice",
  age: 25
};

注释:keyof T 获取 T 的所有键名(即属性名);P in K 是映射类型,表示遍历 K 中的每个键。Pick 是 TypeScript 内置工具类型,用于提取对象的部分属性。


总结:泛型是 TypeScript 的“万能钥匙”

TypeScript 泛型不是一时兴起的语法糖,而是让代码更安全、更可复用的核心机制。它让开发者在不牺牲类型安全的前提下,写出高度通用的函数、接口和类。

从一个简单的 identity 函数,到复杂的 Pick 工具类型,泛型贯穿了整个 TypeScript 的类型系统。掌握它,意味着你真正进入了“类型编程”的世界。

无论你是初学者还是中级开发者,只要你在项目中遇到“我要处理多种类型但不想写重复代码”的场景,就该想到 TypeScript 泛型。

记住:泛型不是为了炫技,而是为了写出更健壮、更易维护的代码

继续练习,多写几个泛型函数,多定义几个泛型接口,你很快就会发现,它已经成了你日常开发中不可或缺的一部分。