extends 是 TypeScript 中最“精神分裂”的关键字。因为它在不同的场景下,含义截然不同。
你需要记住它的 三种形态:
继承 (Inheritance): "我是你的儿子"(扩充)。
泛型约束 (Constraint): "你必须满足这个标准"(限制)。
条件判断 (Conditional): "你是不是它的子集?"(判断)。
1. 形态一:接口继承 (Inheritance)
含义: 扩展、扩充。
这是最符合直觉的用法,和面向对象编程(Java/C#)一样。
场景: 你已经有一个基础接口,想在此基础上增加新属性。
TypeScript
interface Animal {
move(): void;
}
// Dog 继承了 Animal,不仅要实现 move,还要有 bark
interface Dog extends Animal {
bark(): void;
}
const dog: Dog = {
move: () => console.log("Walking"),
bark: () => console.log("Woof!")
};
这里 extends 的意思是:Dog 包含了 Animal 的所有特征,并且还有更多。
2. 形态二:泛型约束 (Generic Constraints)
含义: 限制、规范。
这是泛型编程中的核心。如果不加约束,泛型 T 可以是任何东西。
场景: 这里的 extends 意思是 “必须包含” 或 “必须是它的子类”。
假设你要写一个函数,打印参数的 length 属性。
TypeScript
// ❌ 报错:TS 不知道 T 到底有没有 length 属性
function getLength<T>(arg: T) {
return arg.length;
}
// ✅ 正确:使用 extends 约束 T
// 翻译:我不关心 T 到底是什么,但它必须至少包含 { length: number } 这个结构
function getLengthV2<T extends { length: number }>(arg: T) {
return arg.length;
}
getLengthV2("hello"); // ✅ string 有 length
getLengthV2([1, 2]); // ✅ array 有 length
getLengthV2(123); // ❌ 报错:number 没有 length
这里的重点: T extends { length: number } 并不意味着 T 只能是 { length: number },而是说 T 是 { length: number } 的超集(必须要满足这个形状)。
3. 形态三:条件类型 (Conditional Types)
含义: 判断、分配。
这是 TS 类型体操的核心逻辑,类似于编程语言中的 if ... else 或三元运算符。
语法: Type = T extends U ? X : Y
翻译: 如果 T 可以赋值给 U,那么类型就是 X,否则是 Y。
TypeScript
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true ("hello" 是 string 的子集)
type B = IsString<123>; // false
难点:为什么叫“可赋值性”检查?
在 TS 中,extends 实际上是在检查 集合论 (Set Theory) 关系。
TypeScript
type Res = "a" extends string ? true : false;
// 结果:true
// 原因:"a" 是 string 这个大集合里的一个小点。
高级特性:分发 (Distributive)
当 extends 前面的参数是 联合类型 (Union Types) 时,它会触发“分发”机制(像 forEach 循环一样)。
TypeScript
// 定义一个工具:如果是 null 或 undefined 就剔除
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null | number>;
// 拆解过程:
// 1. string extends null|undefined ? -> No -> 保留 string
// 2. null extends null|undefined ? -> Yes -> 返回 never (丢弃)
// 3. number extends null|undefined ? -> No -> 保留 number
// 最终结果:string | number
总结对照表
终极面试题:keyof + extends
这俩经常一起出现,构成 TS 中最常见的约束组合:
TypeScript
// K extends keyof T
// 翻译:K 必须是 T 的键名之一
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "Gemini", age: 18 };
getProperty(user, "name"); // ✅
getProperty(user, "sex"); // ❌ 报错:user 里没有 "sex" 这个 key
这里 extends 限制了第二个参数 key,它的值不能瞎填,必须属于 user 对象已有的属性名。
我能为你做的下一步:
目前为止,我们已经把 TS 最核心的关键字(type/interface, unknown/never, infer, extends)都梳理了一遍。
要不要进入实战环节?我可以为你展示 "如何利用这些知识,手写一个 Vue 3 的 ref 或 reactive 的类型定义",看看它们是如何做到自动推导类型的?