TS泛型编程实战
# 04.TS泛型编程实战
泛型是 TS 类型系统的灵魂——让你写"一次定义,多种类型复用"的代码。从约束到条件类型,从 infer 到实战模式。
# 1. 案例引入
# 1.1 没有泛型的痛苦
// 需求:返回数组第一个元素
function first(arr: number[]): number { return arr[0]; } // 只能 number[]
function firstStr(arr: string[]): string { return arr[0]; } // 再来一个 string[]
function firstUser(arr: User[]): User { return arr[0]; } // 又来一个 User[]
// 用 any?失去类型安全
function firstAny(arr: any[]): any { return arr[0]; }
const n = firstAny([1, 2, 3]); // n: any —— 后续完全失去类型提示
n.toFixed(); // 编译不会报错,但如果实际是 string 就崩了
# 1.2 泛型登场
function first<T>(arr: T[]): T {
return arr[0];
}
const n = first([1, 2, 3]); // T = number → n: number
const s = first(["a", "b", "c"]); // T = string → s: string
const u = first([{ name: "张三" }]); // T = { name: string } → u 类型完整保留
泛型的本质:不是"可以接受任何类型",而是"延迟确定具体类型,但不丢失类型信息"。
# 2. 泛型函数
# 2.1 基本语法与类型推断
// <T> —— 类型参数,调用时确定
function identity<T>(arg: T): T {
return arg;
}
// 显式指定类型
let a = identity<string>("hello"); // T = string
// 自动推断(最常用)
let b = identity("hello"); // T 自动推断为 "hello"
let c = identity(42); // T 自动推断为 number
# 2.2 多类型参数
// 多个泛型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const p = pair("name", 42); // [string, number]
// K = string, V = number
# 2.3 泛型在数组与 Promise 中的应用
// 数组
function reverse<T>(arr: T[]): T[] {
return arr.reverse();
}
// Promise
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json(); // 返回类型与 T 绑定
}
const user = await fetchData<User>("/api/user");
// user: User
# 3. 泛型约束(extends)
# 3.1 为什么要约束
function getLength<T>(arg: T): number {
// return arg.length; // ❌ T 上不存在属性 length
return 0;
}
# 3.2 extends 约束语法
// 约束 T 必须有 length 属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length; // ✅ T 保证有 length
}
getLength("hello"); // ✅ string 有 length
getLength([1, 2, 3]); // ✅ 数组有 length
// getLength(42); // ❌ number 没有 length
# 3.3 类型参数之间的约束
// T 必须有 K 属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "张三", age: 25 };
getProperty(user, "name"); // ✅ string
getProperty(user, "age"); // ✅ number
// getProperty(user, "email"); // ❌ email 不存在于 user
这是泛型最常用的模式——约束第二个类型参数必须是第一个的 key。
# 3.4 多重约束
interface HasId { id: number; }
interface HasTimestamp { createdAt: Date; }
// T 必须同时满足 HasId 和 HasTimestamp
function save<T extends HasId & HasTimestamp>(record: T): void {
console.log(`保存 ${record.id}, 创建于 ${record.createdAt}`);
}
# 4. 泛型接口与泛型类
# 4.1 泛型接口
// 泛型接口的基础用法
interface Box<T> {
value: T;
}
let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };
// 实战:泛型仓库模式
interface Repository<T> {
findById(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<void>;
delete(id: number): Promise<void>;
}
// User 仓库
class UserRepository implements Repository<User> {
async findById(id: number) { /* ... */ }
async findAll() { /* ... */ }
async save(user: User) { /* ... */ }
async delete(id: number) { /* ... */ }
}
# 4.2 泛型类
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];
}
get size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined
# 4.3 泛型类的静态成员
class Utility<T> {
// 静态成员不能使用类的泛型类型参数
// static default: T; // ❌ 静态成员属于类本身,不属于实例
// 静态方法可以有自己独立的泛型
static create<T>(value: T): Utility<T> {
return new Utility<T>();
}
}
# 5. keyof 与 typeof(类型上下文)
# 5.1 keyof:获取对象类型的 key 联合
interface Person {
name: string;
age: number;
email: string;
}
type PersonKeys = keyof Person;
// "name" | "age" | "email"
// 与泛型约束结合——安全的属性访问
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const p: Person = { name: "张三", age: 25, email: "a@b.com" };
getValue(p, "name"); // ✅ string
getValue(p, "age"); // ✅ number
// getValue(p, "foo"); // ❌ 编译错误
# 5.2 typeof 在类型上下文
// 值空间 → 类型空间
const config = {
host: "localhost",
port: 3000,
ssl: true
};
// 获取 config 的类型
type Config = typeof config;
// { host: string; port: number; ssl: boolean }
// 配合 keyof 使用
type ConfigKey = keyof typeof config;
// "host" | "port" | "ssl"
// 实战:从函数返回值提取类型
function makeUser(name: string, age: number) {
return { name, age, createdAt: new Date() };
}
type User = ReturnType<typeof makeUser>;
// { name: string; age: number; createdAt: Date }
# 6. 泛型条件类型
# 6.1 条件类型语法
// T extends U ? X : Y
// 如果 T 可赋值给 U,则类型为 X,否则为 Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
# 6.2 分布式条件类型
// 当 T 是联合类型时,条件类型会"分发"
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// (string[] | number[]) ← 不是 (string | number)[]
// 这是条件类型最强大的特性——逐成员处理联合
type Exclude<T, U> = T extends U ? never : T;
type T1 = Exclude<"a" | "b" | "c", "a" | "c">; // "b"
type Extract<T, U> = T extends U ? T : never;
type T2 = Extract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
# 6.3 阻止分发
// 用 [] 包裹阻止分发
type NoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = NoDistribute<string | number>;
// (string | number)[] ← 不会分发,整个联合放入 []
# 7. infer 关键字
# 7.1 infer 只能在条件类型中使用
// infer R:声明 R 以捕获"推断出的类型"
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 应用
type FnReturn = ReturnType<() => string>; // string
type FnReturn2 = ReturnType<typeof Math.max>; // number
# 7.2 infer 的四种经典模式
// 1. 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
// 2. 提取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type E1 = ArrayElement<string[]>; // string
type E2 = ArrayElement<number[]>; // number
// 3. 提取 Promise 包裹的类型
type Awaited<T> = T extends Promise<infer V> ? V : T;
type V1 = Awaited<Promise<string>>; // string
type V2 = Awaited<string>; // string(不包装 Promise 原样返回)
// 4. 提取构造函数实例类型
type InstanceType<T extends new (...args: any[]) => any> =
T extends new (...args: any[]) => infer I ? I : never;
class MyClass { name = "x"; }
type I = InstanceType<typeof MyClass>; // MyClass
# 7.3 infer 的递归推断
// 递归展开 Promise(直到非 Promise 类型)
type DeepAwaited<T> = T extends Promise<infer V>
? DeepAwaited<V>
: T;
type D1 = DeepAwaited<Promise<Promise<number>>>; // number
# 8. 实战案例:类型安全的 API 客户端
// 定义 API 路由映射
interface APIRoutes {
"/api/user": { id: number; name: string };
"/api/users": { id: number; name: string }[];
"/api/login": { token: string; expiresAt: number };
}
// 泛型 API 客户端——完全类型安全
class ApiClient<Routes extends Record<string, any>> {
constructor(private baseUrl: string) {}
async get<K extends keyof Routes & string>(
path: K
): Promise<Routes[K]> {
const response = await fetch(`${this.baseUrl}${path}`);
return response.json();
}
async post<K extends keyof Routes & string, Body extends Routes[K]>(
path: K,
body: Body
): Promise<Routes[K]> {
const response = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
return response.json();
}
}
const client = new ApiClient<APIRoutes>("https://api.example.com");
// 类型自动推断!
const user = await client.get("/api/user"); // { id: number; name: string }
const users = await client.get("/api/users"); // { id: number; name: string }[]
// client.get("/api/nonexistent"); // ❌ 编译错误
# 9. 速查表
| 语法 | 含义 | 示例 |
|---|---|---|
<T> | 声明类型参数 | function id<T>(x: T): T |
T extends U | 泛型约束 | <T extends HasLength> |
K extends keyof T | 约束为对象 key | (obj: T, key: K) |
T[K] | 索引访问(取属性类型) | T["name"] |
T extends U ? X : Y | 条件类型 | IsString<T> |
infer R | 捕获推断类型 | ReturnType<T> |
keyof T | 获取 T 的所有 key | keyof Person |
typeof x | 获取值的类型 | typeof config |
泛型三问(写泛型前自检):
- 有没有多个类型共享同一个逻辑?(→ 泛型函数)
- 不同实体需要相同的结构契约?(→ 泛型接口/类)
- 类型之间有关联关系?(→ 泛型约束 + keyof)
下一篇:05.TS高级类型编程
上次更新: 2026/06/24, 12:59:24