泛型与约束与 keyof
零、一句话定位
-
泛型(Generics):让函数 / 类 / 接口”参数化类型”,做到”类型也能当参数传”。
-
约束(constraint,
extends):给泛型加”门槛”,限制它能接受的类型范围。 -
keyof:取一个对象类型的所有键名组成的联合类型(union)。
英文全称:Generics = 泛型;constraint = 约束;union type = 联合类型;index type = 索引类型。
一、生活化类比
-
泛型像模具:你塞进什么材料(类型
T),产出的产品就是那种材料做的——不用为每种材料开一条生产线。 -
约束
extends像机器只收合格原材料:不是啥都让进,必须符合某个规格。 -
keyof像拿出对象的钥匙串:得到”所有钥匙名(键)“的清单,用来保证”按钥匙取东西”不会拿错。
// 泛型:返回啥类型,就得到啥类型
function identity<T>(v: T): T { return v }
// 约束:T 必须有 length 属性
function logLen<T extends { length: number }>(v: T): number { return v.length }
// keyof:K 只能是 obj 的键
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
二、为什么需要约束
没有约束时,T 是”任意类型”,你不能在它身上调用任何具体方法(TS 不知道它有没有)。extends 告诉 TS:“放心,T 至少有这些东西”,于是你能安全访问 v.length。
三、keyof + 泛型 = 类型安全的取值
K extends keyof T 保证 key 一定是 obj 真实存在的键;返回值类型 T[K] 自动跟随”这个键对应的值类型”。这是 TS 里做”类型安全 getter”的标准写法。
四、要点图解
graph TD A[泛型 T] --> B{需要限制范围?} B -->|是| C["约束 T extends 形状"] B -->|否| D[任意类型] C --> E["配合 keyof 锁死键名"] E --> F["T[K] 自动推导返回值类型"]
写给初学者:每个概念都从”为什么需要它”开始讲,配合通俗比喻和完整可运行代码。
为什么需要泛型?
// ❌ 用 any——丢失类型信息
function identity(value: any): any {
return value;
}
const result = identity("hello"); // result 是 any,不是 string
// ❌ 为每种类型写一个函数——重复
function identityString(value: string): string { return value; }
function identityNumber(value: number): number { return value; }
// ✅ 泛型——一个函数搞定所有类型,且保留类型信息
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // T = string,返回值类型是 string
const num = identity(42); // T = number
str.toUpperCase(); // ✅ TypeScript 知道 str 是 string
num.toFixed(2); // ✅ TypeScript 知道 num 是 number
泛型就是”类型参数”——调用时才确定是什么类型,但确定后 TypeScript 就记住了。
通俗比喻:泛型就像快递柜的”万能格口”——不管放进去的是包裹、文件还是外卖,取出来的时候系统都记得你放的是什么,给你对应的处理方式。而
any就是一个”失忆格口”——放进去是啥取出来就忘了。
泛型函数
// 返回数组的第一个元素
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNum = getFirst([1, 2, 3]); // T = number
const firstStr = getFirst(["a", "b", "c"]); // T = string
// 手动指定类型参数(一般不需要,TS 能自动推断)
const first = getFirst<number>([1, 2, 3]);
// 多个类型参数
function makePair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const pair1 = makePair("name", "张三"); // K=string, V=string
const pair2 = makePair("age", 25); // K=string, V=number
泛型约束(extends)
泛型默认”什么类型都行”,但有时候你需要限制:
interface HasLength { length: number; }
function getLength<T extends HasLength>(value: T): number {
return value.length; // ✅ TS 知道 T 一定有 length
}
getLength("hello"); // ✅ string 有 length
getLength([1, 2, 3]); // ✅ 数组有 length
// getLength(42); // ❌ number 没有 length
// keyof 约束
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"); // ❌ user 没有 email 属性
通俗比喻:泛型约束就像餐厅的着装要求——“商务休闲装”泛型约束了 T 必须是某种风格,但具体穿衬衫还是西装由你决定。
泛型接口和泛型类型
// 泛型接口
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
interface User { name: string; age: number; }
// data 是 User
const userResponse: ApiResponse<User> = {
code: 200,
message: "成功",
data: { name: "张三", age: 25 },
};
// data 是 string[]
const listResponse: ApiResponse<string[]> = {
code: 200,
message: "成功",
data: ["张三", "李四"],
};
// 泛型类型别名
type ApiResult<T> = {
success: boolean;
data: T;
error?: string;
};
泛型的实际应用场景
// 场景 1:通用的 API 分页响应
interface PaginatedResponse<T> {
total: number;
page: number;
pageSize: number;
items: T[];
}
type UserListResponse = PaginatedResponse<User>;
// 场景 2:通用的状态管理
interface State<T> {
value: T;
loading: boolean;
error: string | null;
}
// 场景 3:通用的过滤函数
function myFilter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = myFilter(numbers, (n) => n % 2 === 0); // [2, 4, 6]
速查表
| 概念 | 一句话解释 | 关键代码 |
|---|---|---|
| 泛型 | 类型参数,调用时才确定类型 | function f<T>(x: T): T |
| 泛型约束 | 限制泛型的范围 | <T extends { length: number }> |
keyof | 获取对象所有 key 的联合类型 | keyof User |
| 泛型接口 | 接口带类型参数 | interface Box<T> { value: T } |
速记卡(面试闪卡)
Q1:一句话讲清「泛型与约束与 keyof」到底是什么?
A:TS 泛型把类型当参数一次写通多类型,extends 约束类型形状,keyof 取对象键集合,组合出类型安全的取值。
Q2:为什么需要泛型:any 会丢类型 —— 怎么理解? —— 怎么理解?
A:没有泛型时,想写”返回传入的值”只能 any,但 any 让 TypeScript 丢掉类型信息,等于对它说”别检查了”。生活类比:泛型像快递的通用纸箱,箱子贴了标签(T)记着里面装的是书还是鞋,取件时知道;any 则是失忆纸箱,放进去是啥取出来就忘了。英文术语:Generics(泛型)、type parameter(类型参数)。
Q3:泛型约束 extends:限制类型范围 —— 怎么理解? —— 怎么理解?
A:泛型默认”啥类型都行”,但有时你要访问 v.length,T 就得有 length。约束写法 <T extends { length: number }>,意思是”T 必须至少带 length”。生活类比:extends 像餐厅着装要求”商务休闲”——限制你穿的风格,但具体穿衬衫还是西装随你定。TS 看的是形状不是血缘(结构化类型)。英文术语:constraint(约束)、structural typing(结构化类型)。
Q4:keyof + 泛型 = 类型安全的取值 —— 怎么理解? —— 怎么理解?
A:keyof T 得到 T 所有键的联合类型(如 keyof {a:1,b:2} = “a” | “b”)。经典写法 getProp<T, K extends keyof T>(obj, key): T[K]——K 被约束成 T 的真键,返回值类型跟着键走。生活类比:keyof 像一把钥匙串,K extends keyof T 保证你拿的是真钥匙,拿错钥匙(不存在的属性)编译期直接报错。英文术语:keyof(索引类型查询)、indexed access type(索引访问类型)。
Q5:泛型接口/类型 + 实际应用 —— 怎么理解? —— 怎么理解?
A:泛型不止用于函数,还能写泛型接口(interface Box
Q6:核心速记主线有哪些?
-
泛型:类型当参数,调用时推断,替代 any 保类型
-
约束:<T extends 形状> 限制 T 必须满足结构
-
keyof:取对象所有键的联合类型,配合 T[K] 安全取值
-
应用:泛型接口/类型别名、useState、ApiRes、map
口诀
A:泛型像通用纸箱,标签记着里面装;
any 失忆丢类型,一参通吃遍四方;
extends 约束定形状,结构满足即放行;
keyof 取键配 T[K],安全取值编译帮。
相关链接
-
📋 目录:00-TypeScript
-
📚 学习清单:八股文学习路线图 > 十一、React-TS-JS
-
🔗 interface 与 type — 形状声明的基础
-
🔗 顶部类型与底部类型 — 约束里的边界情况