第 7 章:泛型
学习目标
- 理解泛型的本质(类型参数化)
- 学会函数、接口、类的泛型
- 掌握泛型约束(extends)
- 避开默认类型与条件推断的坑
一、为什么需要泛型
typescript
// 不带泛型:写三遍
function getNumber(arg: number): number { return arg; }
function getString(arg: string): string { return arg; }
// 带泛型:写一遍
function identity<T>(arg: T): T {
return arg;
}
identity<number>(42); // T = number
identity<string>('Tom'); // T = string
identity(123); // 推断 T = number二、泛型函数
typescript
// 数组第一个元素
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first<number>([1, 2, 3]); // 1,推断 number
first<string>(['a', 'b']); // 'a'
first([]); // undefined
// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const p = pair<string, number>('age', 18); // ['age', 18]⚠️ 坑 1:
first<T>和first(T)看起来一样,但只有尖括号版本是泛型;括号是 JS 函数调用。
三、泛型接口
typescript
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: 'Tom' };
// API 响应通用类型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
const userRes: ApiResponse<User> = {
code: 0,
message: 'ok',
data: { id: 1, name: 'Tom' },
};四、泛型类
typescript
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numStack = new Stack<number>();
numStack.push(1);
numStack.push('Tom'); // ❌ 类型不匹配⚠️ 坑 2:泛型类的静态成员不能使用类型参数:
typescript
class Box<T> {
static value: T; // ❌ Static members cannot reference class type parameters
static fn(x: T): void {} // ❌
}五、泛型约束(extends)
typescript
// 约束必须有 length 属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength('hello'); // ✅ string 有 length
logLength([1, 2]); // ✅ array 有 length
logLength(123); // ❌ number 没有 length使用 keyof 约束对象 key:
typescript
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Tom' };
getProp(user, 'name'); // string
getProp(user, 'age'); // ❌ 'age' 不在 keyof T六、多个约束(交叉)
typescript
interface Named { name: string; }
interface Aged { age: number; }
function greet<T extends Named & Aged>(p: T): void {
console.log(`${p.name}, ${p.age}`);
}
greet({ name: 'Tom', age: 18 }); // ✅
greet({ name: 'Tom' }); // ❌ 缺 age七、默认类型参数
typescript
interface ApiResponse<T = unknown> {
code: number;
data: T;
}
const r1: ApiResponse = { code: 0, data: 'anything' }; // T = unknown
const r2: ApiResponse<User> = { code: 0, data: { id: 1 } }; // T = User
// 函数默认类型
function create<T = string>(): T {
return '' as T;
}⚠️ 坑 3:默认类型只在不指定且推断不出时生效;显式传
null或undefined不会触发默认值。
八、条件类型与 infer(预告)
typescript
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = MyReturnType<() => string>; // string
type B = MyReturnType<() => number>; // number
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type X = ElementOf<string[]>; // string九、实战:通用 CRUD
typescript
interface Entity { id: number }
interface Repository<T extends Entity> {
findById(id: number): T | null;
save(entity: T): T;
delete(id: number): void;
}
class UserRepo implements Repository<User> {
private users: User[] = [];
findById(id: number): User | null {
return this.users.find(u => u.id === id) ?? null;
}
save(user: User): User {
this.users.push(user);
return user;
}
delete(id: number): void {
this.users = this.users.filter(u => u.id !== id);
}
}十、本章小结
| 要点 | 关键 |
|---|---|
| 泛型 | 类型参数化,<T> 表示"某种类型" |
| 函数泛型 | function fn<T>(x: T): T |
| 接口泛型 | interface Box<T> { value: T } |
| 类泛型 | class Stack<T> { items: T[] } |
| 约束 | <T extends Xxx> 限定 T 的范围 |
| 默认 | <T = string> 不传时用默认 |
| 静态成员 | 不能用类型参数 |
动手练习
- 通用函数:写
reverse<T>(arr: T[]): T[],支持任意类型数组反转 - 泛型接口:定义
Result<T, E = Error>,包含ok: boolean / value?: T / error?: E - 约束实战:写
pluck<T, K extends keyof T>(objs: T[], key: K): T[K][]
下一章:第 8 章:类与继承 →