Skip to content

TypeScript 的 interface 接口

简介

interface 是对象的模板,可以看作是一种类型约定。使用了某个模板的对象,就拥有了指定的类型结构。

interface 可以表示对象的各种语法,它的成员有 5 种形式。

  • 对象的属性
  • 对象的属性索引
  • 对象的方法
  • 独立的函数
  • 构造函数

(1)对象属性

ts
interface Point {
  x: number;
  y: number;
}

(2)对象的属性索引

ts
interface A {
  [prop: string]: number;
}

// 属性的数值索引,其实类似于数组。
interface B {
  [prop: number]: string;
}

const obj: B = ["a", "b", "c"];

(3)对象的方法

ts
interface A {
  f(x: boolean): string;
}
interface B {
  f: (x: boolean) => string;
}
interface C {
  f: { (x: boolean): string }; // 在 f 属性上用函数类型的对象类型写法
}

(4)函数

ts
interface Add {
  (x: number, y: number): number;
}

const myAdd: Add = (x, y) => x + y;

(5)构造函数

ts
interface ErrorConstructor {
  new (message?: string): Error;
}

特性:继承

interface 可以继承其他类型,主要有下面几种情况。

  • 继承其他 interface
  • 继承自 type
  • 继承自 class

继承其他接口

通过使用extends关键字,继承其他 interface,并支持多重继承。

ts
interface Style {
  color: string;
}
interface Shape {
  name: string;
}
interface Circle extends Style, Shape {
  radius: number;
}

extends关键字会从继承的接口里面拷贝属性类型,但要注意

  • 子接口的属性会覆盖父接口的属性。同名属性必须是类型兼容的

  • 多重继承时,如果多个父接口存在同名属性,同名属性必须是类型兼容的

  • 简单来说,同名时,子类型必须比父类型更具体。

interface 继承 type

interface 可以继承type命令定义的对象类型

ts
type Country = {
  name: string;
  capital: string;
};

interface CountryWithPop extends Country {
  population: number;
}

interface 继承 class

interface 还可以继承 class,即继承该类的所有成员。

ts
class A {
  x: string = "";

  y(): boolean {
    return true;
  }
}

interface B extends A {
  z: number;
}

const b: B = {
  x: "",
  y: function () {
    return true;
  },
  z: 123,
};

某些类拥有私有成员和保护成员,如果用 interface 继承这样的类,private 和 protected 属性访问和操作不了,根本无法实现,没有意义

ts
class A {
    private x: string = "";
    protected y: string = "";
}

interface B extends A {
    z: number;
}

// 报错
// Type '{ x: string; y: string; z: number; }' is not assignable to type 'B'. Property 'x' is private in type 'B' but not in type '{ x: string; y: string; z: number; }'
const b: B = {
    x: '1',
    y: '2',
    z: 3
};

特性:接口合并

多个同名接口会合并成一个接口。

ts
interface Box {
  height: number;
  width: number;
}

interface Box {
  length: number;
}

上面示例中,两个Box接口会合并成一个接口,同时有heightwidthlength三个属性。

Web 网页开发经常会对windows对象和document对象添加自定义属性,但是 TypeScript 会报错,因为原始定义没有这些属性。解决方法就是把自定义属性写成 interface,与原生接口合并。

ts
interface Document {
  foo: string;
}

document.foo = "hello";

同名接口合并时,同一个属性如果有多个类型声明,彼此不能有类型冲突,如果同名方法有不同的类型声明,那么会发生函数重载。而且,后面的定义比前面的定义具有更高的优先级。

Document 对象的createElement()方法,它会根据参数的不同,而生成不同的 HTML 节点对象。

ts
interface Document {
  createElement(tagName: any): Element;
}
interface Document {
  createElement(tagName: "div"): HTMLDivElement;
  createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
  createElement(tagName: string): HTMLElement;
  createElement(tagName: "canvas"): HTMLCanvasElement;
}

// 等同于
interface Document {
  createElement(tagName: "canvas"): HTMLCanvasElement;
  createElement(tagName: "div"): HTMLDivElement;
  createElement(tagName: "span"): HTMLSpanElement;
  createElement(tagName: string): HTMLElement;
  createElement(tagName: any): Element;
}

上面示例中,createElement()方法的函数重载,参数为字面量的类型声明会排到最前面,返回具体的 HTML 节点对象。类型越不具体的参数,排在越后面,返回通用的 HTML 节点对象。

如果两个 interface 组成的联合类型存在同名属性,那么该属性的类型也是联合类型。

ts
interface Circle {
  area: bigint;
}

interface Rectangle {
  area: number;
}

declare const s: Circle | Rectangle;

s.area; // bigint | number

上面示例中,接口CircleRectangle组成一个联合类型Circle | Rectangle。因此,这个联合类型的同名属性area,也是一个联合类型。

区分:interface vs type

🐱 相似地

interface命令与type命令作用类似,很多对象类型即可以用 interface 表示,也可以用 type 表示,他们都可以表示对象类型

class命令也有类似作用,通过定义一个类,同时定义一个对象类型。但是,它会创造一个值(一个构造函数),编译后依然存在。

🐶 不同地

(1)范围:type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。

(2)继承:interface可以继承其他类型,type不支持继承,type可以通过 &运算符重新定义类型

ts
type Foo = { x: number };

interface Bar1 extends Foo {
  y: number;
}

type Bar2 = Foo & { y: number };

(3)同名合并:同名interface会自动合并,同名type则会报错。

interface 是开放的,可以添加属性,type 是封闭的,不能添加属性,只能定义新的 type

(4)interface不能包含属性映射(mapping / keyof 运算),type可以。

ts
interface Point {
  x: number;
  y: number;
}

// 正确
type PointCopy1 = {
  [Key in keyof Point]: Point[Key];
};

// 报错
interface PointCopy2 {
  [Key in keyof Point]: Point[Key];
};

(5)this关键字只能用于interface

ts
// 正确
interface Foo {
  add(num: number): this;
}

// 报错
type Foo = {
  add(num: number): this;
};

(6)type 可以扩展原始数据类型,interface 不行。

ts
// 正确
type MyStr = string & {
  type: "new";
};

// 报错
interface MyStr extends string {
  type: "new";
}

(7)type可以通过联合类型和交叉类型表达更复杂的类型。

综上所述,如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用。