TypeScript泛型日常实用指南

Frontend2026-10-04TryQuickToolBox

你正在编写一个适用于任何类型的函数,但TypeScript总是抱怨any。或者你只是为了处理不同的类型而将同一个函数复制了三次。泛型解决了这个问题——它们允许你编写适用于多种类型的代码,同时保持类型安全。但许多开发者要么避免使用它们,要么过度使用。本指南将向你展示如何在日常代码中使用泛型,而不会感到头疼。

什么是泛型,为什么你应该关心?

泛型是一种参数化类型的方式。不是硬编码像string或number这样的类型,而是使用一个类型变量(通常是T),当函数或类被使用时,它会被替换为具体的类型。

为什么这很重要?因为它让你编写可重用的代码,同时保持类型安全。没有泛型,你要么复制代码,要么使用any,这违背了TypeScript的初衷。

泛型函数:构建块

让我们从一个简单的例子开始。假设你想要一个返回数组第一个元素的函数。

function firstElement(arr: any[]): any {
  return arr[0];
}

这可以工作,但你丢失了类型信息。如果你传递一个字符串数组,返回类型是any,而不是string。现在使用泛型:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

现在TypeScript知道,如果你传递一个string[],你会得到string | undefined。类型被保留了。

你也可以使用多个类型参数:

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

泛型接口和类型别名

泛型不仅限于函数。你可以创建泛型接口和类型别名来描述灵活的数据结构。

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

这对于API响应特别有用。不需要为每个端点定义新的接口,你可以定义一个泛型ApiResponse<T>:

interface ApiResponse<T> {
  data: T;
  status: number;
}

type User = { id: number; name: string };

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

泛型约束:添加规则

有时你想限制可以与泛型一起使用的类型。这就是约束的用武之地。使用extends关键字来要求类型具有某些属性。

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

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Error: number doesn't have length

约束使你的泛型更可预测,并防止运行时错误。

泛型类

类也可以是泛型的。这对于像栈、队列或仓库这样的数据结构很方便。

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 top = numberStack.pop(); // number | undefined

何时使用泛型(何时不使用)

泛型很强大,但如果过度使用,它们会使代码更难阅读。这里有一个快速指南:

使用泛型当 避免泛型当
你需要保留类型关系 简单的联合类型或any就足够了
你正在编写可重用的工具 类型总是相同的
你想避免代码重复 它使代码更难理解

一个好的经验法则:如果你发现自己为多种类型编写相同的逻辑,考虑使用泛型。如果你添加了第三个类型参数,而没有人能理解它,那就退一步。

常见模式和最佳实践

真实世界示例:泛型API客户端

让我们用一个实际例子来总结:一个简单的API客户端,它获取数据并返回类型化的响应。

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

这种模式在前端代码中很常见。你获得了类型安全,而不必为每个端点重复自己。

常见问题

TypeScript中的<T>语法是什么意思?

<T>声明了一个类型参数。它是一个占位符,用于在函数、接口或类被使用时指定的类型。你可以给它起任何名字,但T是单个类型的惯例。

我可以使用多个泛型类型参数吗?

是的。你可以根据需要定义任意多个,用逗号分隔:<T, U, V>。使用描述性名称来保持代码可读性。

如何将泛型约束为只接受对象?

使用T extends object。这确保T是一个对象类型,而不是像string或number这样的原始类型。你也可以约束为特定的形状,比如T extends { id: number }。

泛型是编写可维护TypeScript的核心部分。如果你正在处理JSON数据并想快速验证或格式化它,试试我们的JSON Formatter来美化打印和检查你的API响应。