接口继承

接口可以像类一样使用继承来扩展自身的功能。通过继承,我们可以从已有的接口派生出新的接口,实现代码复用和类型组合。

基本继承语法

接口使用 extends 关键字继承另一个接口,子接口会继承父接口的所有成员。

interface Animal {
  name: string
  age: number
}

interface Dog extends Animal {
  breed: string
}

const myDog: Dog = {
  name: "小黑",
  age: 3,
  breed: "拉布拉多"
}

console.log(`${myDog.name} 是一只 ${myDog.breed}`)

Dog 接口继承了 Animalnameage 属性,同时添加了自己的 breed 属性。实现 Dog 接口的对象必须包含这三个属性。

多接口继承

与类不同,接口可以同时继承多个接口,用逗号分隔。这种能力让接口的组合非常灵活。

interface Flyable {
  fly(): void
}

interface Swimmable {
  swim(): void
}

interface Runnable {
  run(): void
}

interface Duck extends Flyable, Swimmable, Runnable {
  name: string
}

class MallardDuck implements Duck {
  name: string

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

  fly(): void {
    console.log(`${this.name} 飞翔`)
  }

  swim(): void {
    console.log(`${this.name} 游泳`)
  }

  run(): void {
    console.log(`${this.name} 奔跑`)
  }
}

const duck = new MallardDuck("唐老鸭")
duck.fly()
duck.swim()
duck.run()

绿头鸭实现了 Duck 接口,而 Duck 继承了三个能力接口。这种设计方式让我们可以把不同的能力拆分成独立的接口,然后按需组合。

继承链

接口可以形成继承链,一个接口继承另一个接口,后者又可以继承其他接口。

interface Entity {
  id: number
}

interface Timestamped extends Entity {
  createdAt: Date
  updatedAt: Date
}

interface User extends Timestamped {
  name: string
  email: string
}

const user: User = {
  id: 1,
  createdAt: new Date(),
  updatedAt: new Date(),
  name: "张三",
  email: "zhangsan@example.com"
}

User 继承 TimestampedTimestamped 继承 Entity,形成了三层继承关系。最终 User 接口包含了所有祖先接口的成员。

覆盖继承的属性

子接口可以重新定义父接口的属性,但类型必须兼容。通常用于将属性类型收窄或添加更具体的约束。

interface BaseConfig {
  host: string
  port: number | string
}

interface ProductionConfig extends BaseConfig {
  port: number
  ssl: boolean
}

const config: ProductionConfig = {
  host: "api.example.com",
  port: 443,
  ssl: true
}

BaseConfigport 可以是数字或字符串,ProductionConfig 将其收窄为只能是数字。这种覆盖是允许的,因为数字类型是 number | string 的子类型。

接口继承类

接口可以继承类,这会继承类的所有成员,包括私有和受保护成员。这种用法相对少见,但在某些场景下很有用。

class Component {
  private id: string
  protected state: any

  constructor(id: string) {
    this.id = id
    this.state = {}
  }
}

interface IPage extends Component {
  render(): void
}

class HomePage implements IPage {
  private id: string
  protected state: any

  constructor() {
    this.id = "home"
    this.state = {}
  }

  render(): void {
    console.log("渲染首页")
  }
}

实现 IPage 接口的类必须包含 Component 的私有和受保护成员,因为接口继承了类的完整结构。

混合继承

接口可以同时继承其他接口和类,实现更复杂的类型组合。

interface Serializable {
  serialize(): string
}

class BaseEntity {
  id: number

  constructor(id: number) {
    this.id = id
  }
}

interface Document extends BaseEntity, Serializable {
  title: string
  content: string
}

class Article implements Document {
  id: number
  title: string
  content: string

  constructor(id: number, title: string, content: string) {
    this.id = id
    this.title = title
    this.content = content
  }

  serialize(): string {
    return JSON.stringify({
      id: this.id,
      title: this.title,
      content: this.content
    })
  }
}

Document 接口同时继承了类和接口,Article 类必须实现所有继承来的成员。

实际应用场景

接口继承在实际项目中常用于构建分层的类型体系。比如定义 API 响应的通用结构:

interface ApiResponse<T> {
  code: number
  message: string
}

interface PagedResponse<T> extends ApiResponse<T> {
  data: T[]
  total: number
  page: number
  pageSize: number
}

interface User {
  id: number
  name: string
}

const response: PagedResponse<User> = {
  code: 200,
  message: "success",
  data: [
    { id: 1, name: "张三" },
    { id: 2, name: "李四" }
  ],
  total: 100,
  page: 1,
  pageSize: 10
}

基础响应只包含状态码和消息,分页响应在此基础上添加了数据和分页信息。这种分层设计让类型定义更加清晰。

继承与合并的区别

接口继承创建的是新的类型,而声明合并则是将同名接口的成员合并到一起。

interface Config {
  host: string
}

interface Config {
  port: number
}

const config: Config = {
  host: "localhost",
  port: 3000
}

上面两个 Config 接口会自动合并,最终包含 hostport 两个属性。这与继承不同,继承需要显式使用 extends 关键字。

小结

接口继承是 TypeScript 类型系统中非常实用的特性。通过单继承和多继承,我们可以构建灵活的类型层次结构,实现代码复用和类型组合。在实际开发中,合理使用接口继承可以让类型定义更加清晰、易于维护,也便于团队协作时统一接口规范。