首页 / TypeScript 入门教程 / 接口的继承

TypeScript 入门教程

接口的继承

本教程共 80 篇 · 第 25 篇 · 更新于 2026-08-10 · 约 8 分钟阅读

TypeScriptTypeScript 入门教程接口继承extends

本节目标:学会用 extends 让接口复用另一个接口的形状,搞懂接口继承类和交叉类型的本质区别。

写 TypeScript 时间长了你会发现,很多对象形状是层层叠加的。比如”用户”有基本的姓名邮箱,“管理员用户”在基础上多了权限列表,“超级管理员”又多了审计日志。每次都从头写一遍所有属性太傻了。

接口的 extends 关键字,就是为这种场景设计的。

单继承

extends 让一个接口继承另一个:

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
  department: string;
}

const emp: Employee = {
  name: "张三",
  age: 28,
  employeeId: 1001,
  department: "技术部",
};

Employee 继承了 Person 的所有属性,再追加自己的。这跟面向对象编程里的”子类继承父类”是一个道理——子接口自动拥有父接口的全部成员。

你可以把继承理解为”复制粘贴但要保持父子关系”:TypeScript 不会真的复制,但类型检查时 Employee 等价于 { name: string; age: number; employeeId: number; department: string }

多继承

一个接口可以同时继承多个接口:

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

// 同时继承两个接口
interface Duck extends Flyable, Swimmable {
  name: string;
}

const donald: Duck = {
  name: "唐老鸭",
  fly() { console.log("飞起来了"); },
  swim() { console.log("游起来了"); },
};

Duck 同时拥有了 Flyablefly()Swimmableswim(),再加自己的 name。多个父接口之间用逗号分隔。

多继承的冲突处理

如果你继承的两个接口有同名属性,TypeScript 会检查它们是否兼容:

interface A {
  x: number;
}

interface B {
  x: string;  // ❌ 跟 A 的 x 冲突
}

// interface C extends A, B {}  // ❌ 报错:属性 "x" 的类型不兼容

如果同名属性类型不一样,编译直接报。这很好——TypeScript 不会替你做”该用哪个”的猜测,而是让你自己解决冲突。

但如果类型完全相同,或者一个能赋值给另一个(比如 string | number 包含 string),那就没问题:

interface A {
  id: string | number;
}

interface B {
  id: string;
}

interface C extends A, B {
  // ✅ B.id 是 A.id 的子类型,继承没问题
  // 最终 id 的类型收窄为 string
}

接口继承类

TypeScript 有个独特的能力:接口可以继承类。

class Point {
  x: number = 0;
  y: number = 0;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

interface Point3D extends Point {
  z: number;
}

const p3: Point3D = { x: 1, y: 2, z: 3 };

注意关键词:接口继承类,只继承成员的类型,不继承实现。 Point3D 拿到了 x: numbery: number 的类型声明,但没有拿到构造函数、也没有 Point 类的任何方法实现。你仍然需要手动构造一个符合形状的普通对象。

那什么场景会用到这个?主要是当你需要”某个对象长得跟某个类的实例一样,但我不想真的 new 那个类”的时候。比如:

class UserModel {
  id: number = 0;
  name: string = "";
  createdAt: Date = new Date();
  private passwordHash: string = ""; // private 成员不会进接口
}

// 只取公开成员的类型,不依赖 UserModel 类本身
interface UserData extends UserModel {}

function renderUser(data: UserData) {
  console.log(data.name);
}

// 可以传普通对象,不需要 new UserModel()
renderUser({ id: 1, name: "Alice", createdAt: new Date() });
Note

类的 privateprotected 成员不会被接口继承。因为接口描述的是公开契约,私有成员不在此列。

extends 与交叉类型(&)的区别

你会在很多代码里看到用 & 组合类型:

type Employee = Person & { employeeId: number };

这跟 interface Employee extends Person { ... } 看起来很相似——都能把两个类型的属性合到一起。但它们有几个关键区别:

特性extends&(交叉类型)
冲突检测同名属性类型不兼容时立即报错不报错,最终类型收窄为 never(使用时才暴露问题)
声明合并同名 interface 可以多次定义自动合并type 不能声明合并
语法仅用于 interface可用于任何类型
性能编译器内部对 interface 有专门优化交叉类型计算更通用

最大的区别在冲突处理上。看这个例子:

interface A {
  x: number;
}

interface B {
  x: string;
}

// ❌ extends 直接报错,告诉你类型冲突
// interface C extends A, B {}

// type 用 & 不会报错...
type D = A & B;
// 但是 D 的 x 类型变成了 never(不存在既 number 又 string 的值)
// const d: D = { x: ??? }; // 你根本无法构造出满足条件的对象

extends 在你写代码的时候就告诉你”这两个接口不兼容,无法合并”,而交叉类型悄悄把 x 收窄成了 never,等你要用的时候才发现根本没法赋值。从开发者体验来看,extends 的错误信息更友好。

小结

  • extends 让接口继承另一个接口的成员,支持同时继承多个。
  • 同名属性冲突时,类型不兼容会直接报错——比交叉类型更早发现。
  • 接口可以继承类,但只拿公开成员的类型声明,不拿实现。
  • extends& 的核心区别:冲突检测方式不同、是否支持声明合并。

掌握了继承,下一章我们来看看对象属性的一些进阶话题——比如只读属性的”深度陷阱”和索引签名的高级用法。