泛型(Generics)
核心问题解析
问题一:为什么 Array<T> 能支持任意类型?
看这个例子:
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; } 接收任意类型
问题二:函数怎么做到“类型参数化”?
我们希望一个函数能“记住”输入的类型,并用于输出:
function first(arr) {
return arr[0];
}这个函数返回 any,不安全。
使用泛型改造:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}调用时:
first([1, 2, 3]); // 推导返回 number | undefined
first(["a", "b"]); // 推导返回 string | undefined关键:T 在调用时被自动推导为实际传入数组的元素类型。
问题三:T extends U 是什么意思?
有时我们不能让 T 是“任意类型”,而是要有一定结构:
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> 声明类型参数:
function identity<T>(value: T): T { ... }
class Box<T> { ... }
interface Result<T> { ... }T 是约定俗成的名字,也可用 U, K, V 等
可声明多个:<T, U, K>
泛型约束(extends)
限制 T 必须满足某种结构:
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. 泛型函数
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. 泛型类
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. 泛型接口
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>)
有时我们希望泛型有“默认值”:
class Box<T = string> {
value: T;
constructor(value: T) {
this.value = value;
}
}使用时:
new Box("hello"); // T = string(默认)
new Box(123); // T = number(推导)
new Box<number>(123); // T = number(显式)应用场景:
- 配置对象的默认类型
- 兼容旧代码
- 减少类型冗余
目标四:理解泛型在集合类与工具类型中的应用
1. 集合类中的泛型
几乎所有集合都基于泛型:
Map<K, V> // 键值对
Set<T> // 唯一值集合
Promise<T> // 异步结果
Array<T> // 数组const userMap = new Map<string, User>();
const idSet = new Set<number>();
const userPromise: Promise<User> = fetchUser();2. 工具类型中的泛型
内置工具类型本质是“泛型函数”:
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> 工具类型:
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,并正确推导返回类型:
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(出队)操作。
class Queue<T> {
private data: T[] = [];
enqueue(item: T): void { }
dequeue(): T | undefined { }
size(): number { }
}验证 Queue<number> 和 Queue<string> 的类型安全。
练习题 3:泛型约束应用
实现一个函数 merge,合并两个对象,要求第二个对象的键必须在第一个对象中存在:
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:默认类型参数
为以下配置接口添加默认类型:
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 提取以下函数的返回值类型:
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>; // 注意:这是函数类型,不是调用