Skip to content
第 101 / 250 章前端⏱ 10 分钟阅读

第 101 章:泛型(Generics)

学习目标

  • 理解泛型的核心思想和应用场景
  • 掌握泛型函数、泛型接口、泛型类的写法
  • 学会泛型约束与默认值
  • 区分泛型与 any/unknown 的边界

一、为什么需要泛型

泛型解决的是「类型的复用性」问题。

1.1 没有泛型时的痛苦

typescript
// ❌ 每个类型写一遍函数
function identityString(value: string): string {
  return value;
}

function identityNumber(value: number): number {
  return value;
}

// 想支持所有类型?用 any
function identityAny(value: any): any {
  return value;  // ❌ 返回值丢失类型
}

1.2 用泛型解决

typescript
// ✅ 一个函数支持所有类型,且保留类型信息
function identity<T>(value: T): T {
  return value;
}

const a = identity('hello');  // 类型: string
const b = identity(123);       // 类型: number
const c = identity({ x: 1 });  // 类型: { x: number }

二、泛型基础

2.1 泛型函数

typescript
// 语法: 在函数名后加 <T>
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);         // 类型: number | undefined
first(['a', 'b']);         // 类型: string | undefined
first([]);                 // 类型: undefined

// 箭头函数写法
const firstArrow = <T,>(arr: T[]): T | undefined => arr[0];
//                                      ^ 加逗号避免与 JSX 冲突

2.2 显式指定类型参数

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

// TS 自动推断
const a = identity(123);            // T = number

// 显式指定
const b = identity<string>('hi');   // T = string

// 多参数
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

pair('age', 18);                    // [string, number]
pair<number, string>(1, 'Tom');     // [number, string]

2.3 多个类型参数

typescript
function swap<A, B>(tuple: [A, B]): [B, A] {
  return [tuple[1], tuple[0]];
}

const result = swap(['hello', 123]);  // [number, string]

三、泛型接口

3.1 函数类型接口

typescript
interface Mapper<T, U> {
  (item: T): U;
}

const numberToString: Mapper<number, string> = (n) => String(n);
const stringLength: Mapper<string, number> = (s) => s.length;

3.2 数据结构接口

typescript
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

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

const res1: ApiResponse<User> = {
  code: 0,
  message: 'ok',
  data: { id: 1, name: 'Tom' }
};

const res2: ApiResponse<User[]> = {
  code: 0,
  message: 'ok',
  data: [{ id: 1, name: 'Tom' }]
};

3.3 内置泛型接口

typescript
// Array<T> 本身就是泛型
const nums: Array<number> = [1, 2, 3];

// Promise<T>
const p: Promise<string> = new Promise((resolve) => resolve('hi'));

// Record<K, V>
type PageInfo = Record<string, number>;

四、泛型类

typescript
class Container<T> {
  private value: T;

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

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

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

const numberBox = new Container(123);     // Container<number>
numberBox.getValue();                       // 123

const stringBox = new Container<string>('hello');  // 显式指定

4.1 实战:通用栈

typescript
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[this.items.length - 1];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

const stack = new Stack<number>();
stack.push(1);
stack.push(2);
stack.pop();   // 2

五、泛型约束(Constraints)

5.1 基础约束

默认 T 可以是任何类型,但有时我们需要限制。

typescript
// ❌ T 没有 length 属性,会报错
function logLength<T>(arg: T): void {
  console.log(arg.length);  // ❌ Property 'length' does not exist on type 'T'
}

// ✅ 用 extends 约束
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);  // ✅ T 一定有 length
}

logLength('hello');         // ✅ string 有 length
logLength([1, 2, 3]);       // ✅ array 有 length
logLength({ length: 10 });  // ✅ 对象有 length
logLength(123);             // ❌ number 没有 length

5.2 keyof 约束

typescript
// 约束 key 必须是对象 T 的键
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Tom', age: 18 };

getProp(user, 'name');  // ✅ string
getProp(user, 'age');   // ✅ number
getProp(user, 'hobby'); // ❌ 'hobby' 不在 user 的键中

5.3 多重约束

typescript
interface A { a: () => void }
interface B { b: () => void }

function process<T extends A & B>(obj: T): void {
  obj.a();
  obj.b();
}

process({ a() {}, b() {} });  // ✅

六、泛型默认值

typescript
// T 默认是 string
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

const arr1 = createArray(3, 'x');   // string[]
const arr2 = createArray<number>(3, 0);  // number[]

七、泛型与条件类型

typescript
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function foo(): number { return 1; }
type FooReturn = MyReturnType<typeof foo>;  // number

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

type NumElement = ElementOf<number[]>;       // number
type StrElement = ElementOf<string[]>;       // string

八、泛型工具函数模式

8.1 函数式编程

typescript
// map: T[] -> U[]
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}

// filter: T[] -> T[]
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

// reduce: T[] -> U
function reduce<T, U>(arr: T[], fn: (acc: U, item: T) => U, init: U): U {
  return arr.reduce(fn, init);
}

8.2 异步函数

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

// 使用
interface User { id: number; name: string }
const user = await fetchData<User>('/api/user/1');
user.id;  // ✅ 类型安全

九、泛型 vs any vs unknown

typescript
// ❌ any: 完全放弃类型检查
function fn1(value: any): any {
  return value;
}

const a = fn1(123);
a.foo.bar.baz;  // 编译过,运行不知道

// ❌ unknown: 类型安全但每次都要断言
function fn2(value: unknown): unknown {
  return value;
}

const b = fn2(123);
b.toFixed();  // ❌ Object is of type 'unknown'

// ✅ 泛型: 保留类型信息
function fn3<T>(value: T): T {
  return value;
}

const c = fn3(123);
c.toFixed();  // ✅ number 类型有 toFixed
特性anyunknown泛型 <T>
类型检查❌ 关闭✅ 严格✅ 保留
调用方类型丢失丢失推断/指定
灵活性最高
何时用迁移期兜底不确定类型首选

十、常见错误

10.1 泛型参数数量不匹配

typescript
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

pair<string>('a', 'b');   // ❌ Expected 2 type arguments, but got 1
pair<string, number>('a', 1);  // ✅

10.2 约束太宽

typescript
// ❌ 约束等于没约束
function process<T extends object>(obj: T) { }

// ✅ 更具体的约束
function process<T extends { id: number }>(obj: T) {
  console.log(obj.id);  // ✅ 一定有 id
}

10.3 忘记类型推断

typescript
// ❌ 写死类型,失去灵活性
function identity(value: any): any {
  return value;
}

// ✅ 泛型让调用方决定类型
function identity<T>(value: T): T {
  return value;
}

十一、实战案例

11.1 通用分页响应

typescript
interface PageParams {
  page: number;
  pageSize: number;
}

interface PageResult<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}

async function fetchPage<T>(url: string, params: PageParams): Promise<PageResult<T>> {
  const query = `?page=${params.page}&pageSize=${params.pageSize}`;
  const res = await fetch(url + query);
  return res.json();
}

// 使用
const userPage = await fetchPage<User>('/api/users', { page: 1, pageSize: 10 });
userPage.items[0].name;  // ✅ 类型安全

11.2 通用缓存

typescript
class Cache<K, V> {
  private store = new Map<K, V>();

  set(key: K, value: V): void {
    this.store.set(key, value);
  }

  get(key: K): V | undefined {
    return this.store.get(key);
  }

  has(key: K): boolean {
    return this.store.has(key);
  }
}

const userCache = new Cache<number, User>();
userCache.set(1, { id: 1, name: 'Tom' });

11.3 通用选择器函数

typescript
// 安全地访问嵌套字段
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map((item) => item[key]);
}

const users = [
  { name: 'Tom', age: 18 },
  { name: 'Jerry', age: 20 }
];

const names = pluck(users, 'name');  // string[]
const ages = pluck(users, 'age');    // number[]

十二、本章小结

要点关键
泛型语法<T> 类型参数化
泛型函数function fn<T>(arg: T): T {}
泛型接口interface Box<T> { value: T }
泛型约束<T extends Constraint> 限制类型范围
keyof 约束<K extends keyof T> 约束键名
默认值<T = string>
vs any泛型保留类型,any 丢失

动手练习

  1. 基础泛型:实现一个 last<T>(arr: T[]): T | undefined 函数
  2. 泛型约束:写一个 sum<T extends number>(arr: T[]): T 函数
  3. 泛型类:实现一个 Queue<T> 队列类,支持 enqueue / dequeue / front
  4. keyof 约束:写一个 setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]) 函数

推荐阅读


下一章第 102 章:高级类型(Advanced Types)

本站基于 VitePress 构建 · 由 Codebook 团队维护