TypeScript 中 extends 三种形态用法区别

作者:Administrator 发布时间: 2025-12-16 阅读量:8 评论数:0

extends 是 TypeScript 中最“精神分裂”的关键字。因为它在不同的场景下,含义截然不同。

你需要记住它的 三种形态

  1. 继承 (Inheritance): "我是你的儿子"(扩充)。

  2. 泛型约束 (Constraint): "你必须满足这个标准"(限制)。

  3. 条件判断 (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

总结对照表

场景

语法示例

含义 (人话)

作用

继承

interface A extends B

我是你的加强版

复用代码,扩展属性

约束

<T extends { id: number }>

你必须有这个低保

保证泛型操作的安全性

条件

T extends U ? X : Y

T 属于 U 吗?

动态计算类型 (类型体操)


终极面试题: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 的类型定义",看看它们是如何做到自动推导类型的?

评论