请解释 TypeScript 中的泛型(generics),如何使用泛型提高代码的复用性?

TypeScript泛型详解与使用建议

泛型(Generics)是TypeScript中用于增强代码复用性和类型安全的核心特性。它允许我们在定义函数、类、接口时使用类型变量,在调用时动态指定具体类型,从而实现逻辑复用同时保持严格的类型检查。

一、泛型核心概念

泛型本质是类型参数化,类似于函数参数。基本语法使用尖括号<>声明类型变量,常用T(Type)、UV等命名:

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

// 调用时显式指定类型
const str = identity<string>("hello"); // str类型为string
// 或依赖类型推断
const num = identity(42); // num类型为number
二、提高代码复用性的典型场景
  1. 通用数据处理函数

避免为不同类型重复编写相同逻辑:

// 非泛型:需为每个类型写单独函数
function printStringArray(arr: string[]) {
  arr.forEach(item => console.log(item));
}
function printNumberArray(arr: number[]) {
  arr.forEach(item => console.log(item));
}

// 泛型方案:一个函数处理所有类型数组
function printArray<T>(arr: T[]): void {
  arr.forEach(item => console.log(item));
}

printArray<string>(["a", "b"]); // 显式指定类型
printArray([1, 2, 3]);          // 自动推断为number[]
  1. API响应结构封装

统一接口返回格式,动态指定data类型:

interface ApiResponse<T> {
  code: number;
  data: T;      // 核心数据字段动态类型
  message: string;
}

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

// 使用泛型接口
const userResponse: ApiResponse<User> = {
  code: 200,
  data: { id: 1, name: "Alice" },
  message: "OK"
};
  1. 类中的类型复用

创建可重用的容器类,保存任意类型值:

class Box<T> {
  private value: T;

  constructor(initialValue: T) {
    this.value = initialValue;
  }

  getValue(): T {
    return this.value;
  }

  setValue(newValue: T): void {
    this.value = newValue;
  }
}

// 存储数字
const numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 42

// 存储对象
const objBox = new Box({ name: "Bob" });
objBox.setValue({ name: "Charlie" }); // 类型自动推断为{ name: string }
三、高级技巧与使用建议
  1. 类型约束(Type Constraints)​

使用extends关键字限制泛型类型必须满足特定结构:

// 确保传入对象有length属性且为number类型
function logLength<T extends { length: number }>(obj: T): void {
  console.log(obj.length);
}

logLength("hello");    // 5
logLength([1, 2, 3]); // 3
// logLength(42);     // 错误:number没有length属性
  1. 默认类型参数

为泛型提供默认类型,简化调用:

interface Pagination<T = unknown> {
  current: number;
  pageSize: number;
  data: T; // 默认类型为unknown
}

// 不指定类型时使用默认值
const page: Pagination = { 
  current: 1, 
  pageSize: 10, 
  data: {} // 类型推断为unknown
};

// 显式指定类型
const userPage: Pagination<User[]> = {
  current: 2,
  pageSize: 20,
  data: [{ id: 1, name: "Alice" }]
};
  1. 泛型与异步请求

封装通用数据请求函数,自动解析响应类型:

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json(); // 自动推断为T类型
}

// 使用示例
interface Post {
  id: number;
  title: string;
}

// 获取帖子列表
const posts = await fetchData<Post[]>("/api/posts");
// posts类型为Post[]
四、实际开发注意事项
  1. 避免过度泛型化

简单场景无需泛型,优先使用具体类型:

// 错误示范:没必要用泛型
function add<T>(a: T, b: T): T {
  return a + b; // 这里会报错,因为TS不知道T是否支持+
}

// 正确做法:明确限制为number
function add(a: number, b: number): number {
  return a + b;
}
  1. 优先依赖类型推断

大多数情况不需要显式指定类型参数:

// 冗余写法
const arr = Array<string> = ["a", "b"];

// 推荐写法
const arr = ["a", "b"]; // 自动推断为string[]
  1. 谨慎处理复杂嵌套

多层泛型可能降低可读性,适当拆分:

// 难以理解的嵌套泛型
type DeepNested<T> = Promise<Array<{ data: T[] }>>;

// 建议拆解
interface Wrapper<T> {
  data: T[];
}
type BetterType<T> = Promise<Wrapper<T>[]>;
  1. 注意类型收缩问题

联合类型与泛型结合时可能需要类型守卫:

function process<T extends string | number>(value: T): T {
  if (typeof value === "string") {
    return value.trim() as T; // 需要类型断言
  }
  return value.toFixed(2) as T;
}
五、总结

合理使用泛型可以显著提升代码复用率和类型安全性,但需注意:

  • 在数据处理函数、集合类、工具类型等场景优先考虑泛型
  • 通过extends约束和默认参数优化使用体验
  • 避免在简单场景或导致类型复杂化的地方使用
  • 结合类型推断减少冗余代码

通过示例中的模式,开发者可以在保持类型安全的前提下,构建灵活且易于维护的TypeScript代码库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值