TypeScript 中的泛型

TypeScript 中的泛型(Generics)是一种创建可复用代码 的方式,它允许我们在定义函数、接口或类时不预先指定具体的类型,而是在使用时传入类型参数,从而在保持类型安全的同时获得极大的灵活性。


一、泛型的基本概念

可以把泛型理解为"类型变量"。就像函数参数可以接受不同的值,泛型参数可以接受不同的类型

用一对尖括号 <> 声明类型变量,通常用 T(Type)表示。

typescript 复制代码
// 不使用泛型:只能用于 number,返回 number
function identityNumber(arg: number): number {
  return arg;
}

// 使用泛型:接受任意类型,并返回相同类型
function identity<T>(arg: T): T {
  return arg;
}

// 使用
const num = identity<number>(42);        // 类型为 number
const str = identity<string>('hello');   // 类型为 string
// 也可以让编译器自动推断类型
const auto = identity(true);             // 类型为 boolean

泛型保证了输入类型和输出类型之间的关联 ,避免了使用 any 导致的类型丢失。


二、泛型的基本语法

1. 泛型函数

typescript 复制代码
// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}
const p = pair('hello', 10); // [string, number]

2. 泛型接口

typescript 复制代码
interface Box<T> {
  value: T;
  getValue(): T;
}

const stringBox: Box<string> = {
  value: 'hello',
  getValue() { return this.value; }
};

3. 泛型类

typescript 复制代码
class Stack<T> {
  private items: T[] = [];
  push(item: T) { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

const numberStack = new Stack<number>();
numberStack.push(1);
const top = numberStack.pop(); // number | undefined

4. 泛型约束(extends

有时需要限制泛型参数必须符合某种结构,可以使用 extends 关键字。

typescript 复制代码
interface Lengthwise {
  length: number;
}

// T 必须具有 length 属性
function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength('hello');    // OK,string 有 length
logLength([1, 2, 3]);  // OK
// logLength(123);      // Error,number 没有 length

5. 泛型默认值

typescript 复制代码
interface Response<T = string> {
  data: T;
  code: number;
}
// 不传默认 string
const res1: Response = { data: 'ok', code: 200 };
// 也可以覆盖
const res2: Response<number> = { data: 123, code: 200 };

三、泛型的典型应用场景

1. 内置工具类型(Utility Types)

TypeScript 提供了大量基于泛型的工具类型,极大地提升了开发效率。

typescript 复制代码
interface User {
  id: number;
  name: string;
  age: number;
}

// Partial<T>:所有属性变为可选
type PartialUser = Partial<User>;

// Pick<T, K>:从 T 中挑选一组属性
type UserPreview = Pick<User, 'id' | 'name'>;

// Record<K, V>:构造以 K 为键,V 为值的对象类型
type PageInfo = Record<string, string>; // { [key: string]: string }

// Omit<T, K>:排除某些属性
type UserWithoutAge = Omit<User, 'age'>;

// Readonly<T>:所有属性变为只读
type ReadonlyUser = Readonly<User>;

2. 封装通用 API 请求

用泛型定义统一的请求响应结构,让每个接口的返回值都有精确的类型。

typescript 复制代码
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

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

// 使用
interface UserInfo {
  id: number;
  name: string;
}
const userRes = await fetchData<UserInfo>('/api/user');
console.log(userRes.data.name); // 有完整的类型提示

3. 前端状态管理(Pinia / Vuex)

定义 Store 时可以传入泛型,让 state、getters、actions 的类型自动推导。

typescript 复制代码
// Pinia 示例
const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2
  }
});
// defineStore 内部大量使用泛型,最终 store.count 是 number,doubleCount 是 number

4. 泛型组件(React / Vue3)

在 Vue3 的 <script setup> 或 React 函数组件中,可以用泛型定义灵活的 Props。

Vue3 示例

typescript 复制代码
// 定义泛型组件 Props
<script setup lang="ts" generic="T">
const props = defineProps<{
  items: T[];
  getKey: (item: T) => string;
}>();
</script>

<template>
  <div v-for="item in items" :key="getKey(item)">
    <slot :item="item" />
  </div>
</template>

React 示例

tsx 复制代码
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

5. 高阶函数与柯里化

定义参数动态、类型关联的高阶函数时,泛型必不可少。

typescript 复制代码
function map<T, U>(arr: T[], fn: (item: T, index: number) => U): U[] {
  return arr.map(fn);
}
const lengths = map(['a', 'bc'], s => s.length); // number[]

6. 事件与表单处理

typescript 复制代码
// 泛型事件处理函数
function handleChange<T extends HTMLInputElement | HTMLSelectElement>(
  e: Event & { target: T }
) {
  console.log(e.target.value); // 能正确识别 value
}

// 表单验证函数
function validate<T extends Record<string, any>>(data: T, rules: Record<keyof T, (val: any) => boolean>) {
  // ...
}

7. 数据结构的通用实现

typescript 复制代码
class Queue<T> {
  private data: T[] = [];
  enqueue(item: T) { this.data.push(item); }
  dequeue(): T | undefined { return this.data.shift(); }
}

四、泛型的好处总结

优势 说明
类型安全 避免 any,在编译期捕获类型错误
代码复用 一套逻辑适用于多种类型,减少重复代码
抽象能力 让函数、组件能够与不同类型协作,而不丢失类型信息
智能推导 IDE 可以根据传入参数自动推断泛型,减少显式标注,提升开发体验
可维护性 类型即文档,一眼就能看出函数期望的输入输出关系

泛型是 TypeScript 类型系统的灵魂,掌握它将使你从"使用类型"的开发者进阶为"设计类型"的开发者,写出既安全又优雅的代码。

相关推荐
IT_陈寒2 小时前
React hooks闭包陷阱把我坑惨了,原来这才是正确用法
前端·人工智能·后端
nnsix2 小时前
MVC、MVP、MVVM 架构 笔记
java·开发语言·前端
qq_420362032 小时前
前端国际化方案
前端·javascript·vue.js·国际化·reactjs
向上的车轮2 小时前
React 19 快速入门:拥抱服务端组件与新特性的现代化开发
前端·javascript·react.js
Smile_2542204182 小时前
vue3 + ts reactive方式清空表单对象
开发语言·前端·javascript
多租户观察室2 小时前
信通院标准体系2.0深度解读:低代码管理平台进入“精品竞争”时代
前端·低代码·程序员
云水一下2 小时前
CSS3从零基础到精通(四):终章大项目——纯CSS构建企业品牌展示网站
前端·css3
147API2 小时前
Claude Opus 4.8 接口与工程落地分析:长任务调用链应该怎么设计
java·前端·数据库
李子琪。2 小时前
Web 漏洞与防御机制实验报告
前端·经验分享