很多人在项目中用过 Array<T>、Promise<T>,
但一看到 <T extends object> 或 keyof T 就发怵。
其实泛型就是"类型的参数",理解了这个抽象,内置工具类型的实现也就一目了然了。
从一个最简单的泛型函数开始
// 不用泛型:any 丢失了类型信息
function identity(arg: any): any {
return arg;
}
const a = identity("hello"); // a: any,类型丢了
// 用泛型:类型跟着参数走
function identity<T>(arg: T): T {
return arg;
}
const b = identity("hello"); // b: "hello",类型完整保留
const c = identity(42); // c: 42
<T> 声明了一个"类型参数",它在调用时被具体的类型替换。
TypeScript 还能根据实参自动推断 T,所以调用时通常不用手写尖括号。
约束 extends:给类型参数画个圈
不带约束的 T 可以是任何类型,很多时候太宽了。用 extends 限定 T 必须满足某个形状:
// T 必须具有 length 属性(string / Array / 自定义类型都满足)
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("abc"); // ✅
logLength([1, 2, 3]); // ✅
logLength(42); // ❌ number 没有 length,编译报错
keyof:取出对象的所有键
keyof T 会生成一个由 T 的所有键组成的联合类型,是很多高级用法的基石:
interface User {
name: string;
age: number;
email: string;
}
// "name" | "age" | "email"
type UserKey = keyof User;
// 经典应用:安全地读取属性
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { name: "suiji", age: 18, email: "a@b.c" };
get(user, "name"); // ✅ string
get(user, "phone"); // ❌ "phone" 不在 User 的键里,编译期拦截
看懂内置工具类型
TypeScript 自带的一批工具类型(Utility Types)其实都是几行代码的"类型函数"。 掌握了上面两个概念,它们的源码并不难读:
// 把每个属性变成可选(?: 表示可选)
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 从 T 中挑出 K 指定的属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 用给定键构造一个统一值类型的对象
type Record<K extends keyof any, V> = {
[P in K]: V;
};
// 实际使用
type PartialUser = Partial<User>; // { name?: string; age?: number; ... }
type NameAndAge = Pick<User, "name" | "age">; // { name: string; age: number }
type StatusMap = Record<"draft" | "done", string>; // { draft: string; done: string }
[P in keyof T] 这种"映射类型"语法,可以理解为:遍历 T 的每个键 P,
逐一生成一个新属性。这是类型层面的 map 操作。
条件类型:类型的 if / else
type IsString<T> = T extends string ? "yes" : "no";
type R1 = IsString<"hello">; // "yes"
type R2 = IsString<42>; // "no"
// 实用例子:取出 Promise 包装里的值类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type A = Unwrap<Promise<number>>; // number
type B = Unwrap<string>; // string
infer 关键字允许在条件类型里"声明一个待推断的类型变量",
它是很多高级工具类型的核心,也是"类型体操"的入口。不过日常开发中,
能读懂它、会用它封装一两个通用工具函数就足够了,不必追求花哨。
泛型在组件与 API 中的实践
// 泛型组件:列表组件接收什么数据,就推导出什么回调类型
interface ListProps<T> {
items: T[];
render: (item: T) => React.ReactNode;
}
// 泛型接口:请求封装
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
// const res: ApiResponse<User[]> = await fetchUsers();
总结:泛型 = 类型的参数;extends 是约束,keyof 是取键,映射类型是遍历,条件类型是分支。这四块拼图到位,TypeScript 的类型世界基本就通了。