Skip to content

泛型(Generics)

核心问题解析

问题一:为什么 Array<T> 能支持任意类型?

看这个例子:

ts
const numbers: Array<number> = [1, 2, 3];
const strings: Array<string> = ["a", "b", "c"];

Array<T> 中的 T 是一个类型参数,它像函数的参数一样,可以“接收”不同的类型。

答案:Array<T> 是一个泛型类型,T 是占位符,在使用时被具体类型(如 number、string)替换,从而实现“一套逻辑,多种类型”。

类比函数:function identity(x) { return x; } 接收任意值

泛型:function identity<T>(x: T): T { return x; } 接收任意类型

问题二:函数怎么做到“类型参数化”?

我们希望一个函数能“记住”输入的类型,并用于输出:

ts
function first(arr) {
return arr[0];
}

这个函数返回 any,不安全。

使用泛型改造:

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

调用时:

ts
first([1, 2, 3]);     // 推导返回 number | undefined
first(["a", "b"]);    // 推导返回 string | undefined

关键:T 在调用时被自动推导为实际传入数组的元素类型。

问题三:T extends U 是什么意思?

有时我们不能让 T 是“任意类型”,而是要有一定结构:

ts
function logLength<T extends { length: number }>(item: T): T {
console.log(item.length);
return item;
}

T extends { length: number } 表示:T 必须是 { length: number } 的子类型

  • 合法调用:logLength("hello")、logLength([1,2])、logLength({ length: 5 })
  • 非法调用:logLength(123) ❌ 数字没有 length 属性

答案:extends 是泛型约束,用于限制类型参数的范围,确保类型安全。

学习目标详解

目标一:理解泛型参数的声明与约束(extends)

1. 声明泛型参数

语法:在函数、类、接口名后用 <T> 声明类型参数:

ts
function identity<T>(value: T): T { ... }
class Box<T> { ... }
interface Result<T> { ... }

T 是约定俗成的名字,也可用 U, K, V 等

可声明多个:<T, U, K>

泛型约束(extends)

限制 T 必须满足某种结构:

ts
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

K extends keyof T:确保 key 是 obj 的有效属性名

防止 getProperty(user, 'email') 这类拼写错误

常见约束模式:

  • T extends string:必须是字符串字面量
  • T extends any[]:必须是数组
  • T extends new () => any:必须是构造函数

目标二:实现泛型函数、泛型类、泛型接口

1. 泛型函数

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

map([1, 2, 3], n => n * 2); // number[]
map(["a", "b"], s => s.toUpperCase()); // string[]

2. 泛型类

ts
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"); // ❌

3. 泛型接口

ts
interface Response<T> {
  data: T;
  status: number;
}

const userRes: Response<User> = { data: user, status: 200 };
const listRes: Response<User[]> = { data: users, status: 200 };

目标三:掌握默认类型参数(<T = string>)

有时我们希望泛型有“默认值”:

ts
class Box<T = string> {
value: T;
constructor(value: T) {
this.value = value;
}
}

使用时:

ts
new Box("hello");     // T = string(默认)
new Box(123);         // T = number(推导)
new Box<number>(123); // T = number(显式)

应用场景:

  • 配置对象的默认类型
  • 兼容旧代码
  • 减少类型冗余

目标四:理解泛型在集合类与工具类型中的应用

1. 集合类中的泛型

几乎所有集合都基于泛型:

ts
Map<K, V>      // 键值对
Set<T>         // 唯一值集合
Promise<T>     // 异步结果
Array<T>       // 数组
ts
const userMap = new Map<string, User>();
const idSet = new Set<number>();
const userPromise: Promise<User> = fetchUser();

2. 工具类型中的泛型

内置工具类型本质是“泛型函数”:

ts
type MyPartial<T> = {
[P in keyof T]?: T[P];
};

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

这些就是 Partial<T>Pick<T, K> 的实现原理。

实战:实现一个 Asyncify<T> 工具类型:

ts
type Asyncify<T> = { [K in keyof T]: T[K] extends Function ? (...args: any[]) => Promise<ReturnType<T[K]>> : T[K] };

// 将同步方法转为异步
interface API {
getUser(id: number): User;
save(data: any): void;
}

type AsyncAPI = Asyncify<API>;
// getUser: (id: number) => Promise<User>

总结:本节核心要点

概念关键点
泛型类型的“参数”,实现逻辑复用
T extends U约束类型范围,确保安全
泛型函数/类/接口构建可复用的类型安全结构
默认类型参数提升易用性,减少冗余
工具类型泛型的高级应用,类型编程基石

练习题

练习题 1:实现泛型函数

实现一个泛型函数 filterNull,过滤掉数组中的 null 和 undefined,并正确推导返回类型:

ts
function filterNull<T>(arr: (T | null | undefined)[]): T[] {
return arr.filter((item): item is T => item != null);
}

// 测试
const mixed = [1, null, 2, undefined, 3];
const numbers = filterNull(mixed); // 类型应为 number[]

练习题 2:泛型类实现栈

实现一个泛型类 Queue<T>,支持 enqueue(入队)和 dequeue(出队)操作。

ts
class Queue<T> {
private data: T[] = [];

enqueue(item: T): void { }
dequeue(): T | undefined { }
size(): number { }
}

验证 Queue<number>Queue<string> 的类型安全。

练习题 3:泛型约束应用

实现一个函数 merge,合并两个对象,要求第二个对象的键必须在第一个对象中存在:

ts
function merge<T, K extends keyof T>(target: T, update: Partial<Pick<T, K>>): T {
return { ...target, ...update };
}

const user = { id: 1, name: "Alice", age: 30 };
merge(user, { name: "Bob" });     // ✅ OK
merge(user, { email: "b@b.com" }); // ❌ 应报错

练习题 4:默认类型参数

为以下配置接口添加默认类型:

ts
interface Options<T = string, U = number> {
formatter: (value: T) => string;
timeout: U;
}

// 验证:
const defaultOpts: Options = {
formatter: s => s,
timeout: 5000
}; // T=string, U=number 自动应用

练习题 5:泛型与工具类型

使用 ReturnType 提取以下函数的返回值类型:

ts
function getUser() {
return { id: 1, name: "Alice" };
}

function getPosts(userId: number) {
return [{ id: 1, title: "Hello" }];
}

type User = ReturnType<typeof getUser>;
type PostList = ReturnType<typeof getPosts>; // 注意:这是函数类型,不是调用