《Go 后端开发者学 TypeScript:extends关键字》

TypeScript 中的 extends 是一个非常重要的关键字,主要有 4 种用法:

  1. 接口继承:interface B extends A
  2. 类继承:class B extends A
  3. 泛型约束:T extends Type
  4. 条件类型:T extends U ? X : Y

其中,前两种比较容易理解,泛型约束和条件类型是 TypeScript 中最值得掌握的部分。

下面通过例子逐一说明。

1. interface extends:接口继承

假设我们定义一个用户接口:

typescript 复制代码
interface User {
  id: number;
  name: string;
}

现在需要定义管理员类型,管理员除了用户属性,还有额外的权限属性。

php 复制代码
interface Admin extends User {
  permissions: string[];
}

此时 Admin 相当于:

ini 复制代码
interface Admin {
  id: number;
  name: string;
  permissions: string[];
}

使用:

css 复制代码
const admin: Admin = {
  id: 1001,
  name: "张三",
  permissions: ["read", "write"]
};

核心理解:Admin extends User 表示 Admin 继承 User 的属性约束。

多接口继承

TypeScript 允许一个接口继承多个接口。

typescript 复制代码
interface User {
  id: number;
  name: string;
}

interface Permission {
  canEdit: boolean;
  canDelete: boolean;
}

interface Admin extends User, Permission {
  role: string;
}

使用:

yaml 复制代码
const admin: Admin = {
  id: 1,
  name: "张三",
  canEdit: true,
  canDelete: false,
  role: "admin"
};

2. class extends:类继承

class extends 与 Java、JavaScript 中的类继承非常相似。

typescript 复制代码
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} is barking`);
  }
}

const dog = new Dog("旺财");

dog.eat();  // 旺财 is eating
dog.bark(); // 旺财 is barking

这里:

scala 复制代码
class Dog extends Animal

表示 Dog 继承 Animal。

Dog 可以使用 Animal 的公共属性和方法,同时还可以定义自己的方法。

super 的使用

如果子类需要自定义构造函数,需要调用 super()。

typescript 复制代码
class Animal {
  constructor(public name: string) {}
}

class Dog extends Animal {
  constructor(
    name: string,
    public age: number
  ) {
    super(name);
  }
}

const dog = new Dog("旺财", 3);

console.log(dog.name); // 旺财
console.log(dog.age);  // 3

super(name) 用于调用父类构造函数。

3. 泛型 extends:约束泛型类型(重点)

这是 TypeScript 中非常常用的写法。

先看普通泛型:

scss 复制代码
function printValue<T>(value: T): T {
  return value;
}

printValue(123);
printValue("hello");
printValue({ name: "张三" });

这里的 T 可以是任意类型。

但如果希望 T 必须具有某些属性,就需要 extends。

示例一:要求对象必须包含 id

typescript 复制代码
interface HasId {
  id: number;
}

function printId<T extends HasId>(value: T): void {
  console.log(value.id);
}

调用:

php 复制代码
printId({ id: 1, name: "张三" }); // ✅

printId({ id: 2, age: 20 });      // ✅

printId({ name: "李四" });        // ❌

解释:

java 复制代码
T extends HasId

表示:

泛型 T 必须满足 HasId 类型的结构要求,至少包含 id: number。

注意,它并不要求 T 与 HasId 完全相同。

示例二:限制泛型必须具有 length 属性

r 复制代码
function getLength<T extends { length: number }>(value: T): number {
  return value.length;
}

使用:

scss 复制代码
getLength("hello");      // ✅ 5
getLength([1, 2, 3]);    // ✅ 3
getLength({ length: 10 }); // ✅ 10

getLength(123); // ❌ number 没有 length 属性

这个例子非常典型。

因为:

r 复制代码
T extends { length: number }

要求 T 必须有一个 length 属性,而且该属性是 number。

示例三:结合 React renderItem

结合你前面问过的 renderItem,可以这样使用:

typescript 复制代码
import type { ReactNode } from "react";

interface BaseItem {
  id: number;
}

interface ListProps<T extends BaseItem> {
  data: T[];
  renderItem: (item: T) => ReactNode;
}

function MyList<T extends BaseItem>({
  data,
  renderItem
}: ListProps<T>) {
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>
          {renderItem(item)}
        </div>
      ))}
    </div>
  );
}

使用:

typescript 复制代码
interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: "张三" },
  { id: 2, name: "李四" }
];

<MyList
  data={users}
  renderItem={(item) => (
    <span>{item.name}</span>
  )}
/>

这里:

java 复制代码
T extends BaseItem

保证了 item.id 一定存在。

同时又保留了其他属性,比如 name。

这就是泛型 extends 最典型的应用场景之一。

4. 条件类型 extends:类型判断(重点)

这个用法与前面几个不太一样。

语法:

java 复制代码
T extends U ? X : Y

可以理解为:

如果 T 可以赋值给 U,则返回类型 X,否则返回类型 Y。

类似于 JavaScript 中的三元运算符:

sql 复制代码
condition ? value1 : value2

但条件类型发生在 TypeScript 的类型系统中,不是运行时判断。

示例一:判断是否为 string

typescript 复制代码
type IsString<T> = T extends string ? true : false;

使用:

ini 复制代码
type A = IsString<string>;  // true

type B = IsString<number>;  // false

type C = IsString<"hello">; // true

这里得到的 true、false 是布尔字面量类型。

示例二:根据类型返回不同结果

typescript 复制代码
type Result<T> = T extends string ? string[] : number[];

使用:

ini 复制代码
type A = Result<string>;
// string[]

type B = Result<number>;
// number[]

这个语法非常适合构建复杂的工具类型。

示例三:联合类型分发

这是条件类型中特别重要的一点。

typescript 复制代码
type ToArray<T> = T extends unknown ? T[] : never;

使用:

ini 复制代码
type Result = ToArray<string | number>;

结果:

ini 复制代码
type Result = string[] | number[];

原因是 T 是裸类型参数,条件类型会对联合类型逐个判断。

相当于:

swift 复制代码
ToArray<string> | ToArray<number>

如果不希望分发,可以这样写:

css 复制代码
type ToArray<T> =
  [T] extends [unknown] ? T[] : never;

此时:

ini 复制代码
type Result = ToArray<string | number>;
// (string | number)[]

5. extends 和 keyof 结合使用

这在实际开发中非常常见。

假设有一个对象:

typescript 复制代码
interface User {
  id: number;
  name: string;
  age: number;
}

希望编写一个函数,安全地获取对象属性。

r 复制代码
function getProperty<T, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

使用:

ini 复制代码
const user = {
  id: 1,
  name: "张三",
  age: 25
};

const name = getProperty(user, "name");
// 推断为 string

const age = getProperty(user, "age");
// 推断为 number

getProperty(user, "address"); // ❌

我们拆开理解:

java 复制代码
K extends keyof T
  • T:对象类型。
  • keyof T:获取对象所有属性名组成的联合类型。
  • K extends keyof T:限制 K 必须是 T 的合法属性名。
  • T[K]:获取对应属性的值类型。

例如:

bash 复制代码
type UserKeys = keyof User;

// "id" | "name" | "age"

所以:

java 复制代码
K extends keyof User

就相当于:

javascript 复制代码
K extends "id" | "name" | "age"

这是一种非常常见的类型安全编程方式。

6. extends 和 infer 结合使用(进阶)

infer 可以在条件类型中推断出某个类型。

例如获取数组元素类型:

r 复制代码
type ElementType<T> =
  T extends (infer U)[] ? U : T;

使用:

ini 复制代码
type A = ElementType<string[]>;
// string

type B = ElementType<number[]>;
// number

type C = ElementType<boolean>;
// boolean

解释:

scss 复制代码
T extends (infer U)[]

可以理解为:

如果 T 是数组,就把数组元素类型提取出来,并命名为 U。

再举个例子,提取函数的返回值类型:

typescript 复制代码
type MyReturnType<T> =
  T extends (...args: any[]) => infer R
    ? R
    : never;

使用:

bash 复制代码
function getUser() {
  return {
    id: 1,
    name: "张三"
  };
}

type UserType = MyReturnType<typeof getUser>;

结果:

ini 复制代码
type UserType = {
  id: number;
  name: string;
};

实际上,TypeScript 已经内置了 ReturnType<T>,原理与此类似。

7. 四种 extends 用法总结

用法 示例 作用
接口继承 interface B extends A 继承接口结构
类继承 class B extends A 继承父类
泛型约束 T extends User 限制泛型允许的类型
条件类型 T extends U ? X : Y 根据类型兼容关系选择类型

最容易混淆的两个用法

泛型约束

r 复制代码
function test<T extends string>(value: T) {
  return value;
}

含义:T 必须是 string 的子类型(可赋值给 string)。

条件类型

typescript 复制代码
type Test<T> = T extends string
  ? number
  : boolean;

含义:如果 T 满足 string 类型约束,选择 number,否则选择 boolean。

建议学习顺序:

先掌握 interface extends,然后重点研究 T extends Type、K extends keyof T,最后再学习 T extends U ? X : Y 和 infer。

因为在实际 React + TypeScript 项目中,泛型约束和 keyof 的组合会特别频繁地出现,尤其是列表组件、表格组件、表单组件和公共工具函数。

相关推荐
张张123y18 分钟前
DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表
开发语言·javascript·人工智能·开源·ecmascript·后端开发·ai agent
Penge6665 小时前
《Go 后端开发者学 TypeScript:typescript 类型断言》
javascript
Penge6665 小时前
《Go 后端开发者学 TypeScript:Symbol》
javascript
三*一7 小时前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
FungLeo9 小时前
成为全栈·基础补充·Node.js 运行时:事件循环与异步
javascript·node.js·事件循环·后端开发·异步编程·成为全栈
Penge6669 小时前
《Go 后端开发者学 TypeScript:类型别名》
javascript
Penge66617 小时前
《Go 后端开发者学 TypeScript:数组和元组》
javascript
Penge66617 小时前
《Go 后端开发者学 TypeScript:export关键字大全》
javascript
Penge66617 小时前
《Go 后端开发者学 TypeScript:从零理解 async、await 与 Promise》
javascript