TypeScript 的 interface 接口

81 阅读4分钟

简介

interface 是对象的模板,可以看作是一种类型约定,中文译为“接口”。 方括号运算符可以取出 interface 某个属性的类型。

interface Foo {
  a: string;
}

type A = Foo['a']; // string

(1)对象属性

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

(2)对象的属性索引 一个接口中,最多只能定义一个字符串索引。

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

(3)对象的方法

对象的方法共有三种写法。

// 写法一
interface A {
  f(x: boolean): string;
}

// 写法二
interface B {
  f: (x: boolean) => string;
}

// 写法三
interface C {
  f: { (x: boolean): string };
}

属性名可以采用表达式,所以下面的写法也是可以的。

const f = 'f';

interface A {
  [f](x: boolean): string;
}

interface 里面的函数重载,不需要给出实现。但是,由于对象内部定义方法时,无法使用函数重载的语法,所以需要额外在对象外部给出函数方法的实现。

interface A {
  f(): number;
  f(x: boolean): boolean;
  f(x: string, y: string): string;
}

function MyFunc(): number;
function MyFunc(x: boolean): boolean;
function MyFunc(x: string, y: string): string;
function MyFunc(x?: boolean | string, y?: string): number | boolean | string {
  if (x === undefined && y === undefined) return 1;
  if (typeof x === "boolean" && y === undefined) return true;
  if (typeof x === "string" && typeof y === "string") return "hello";
  throw new Error("wrong parameters");
}

const a: A = {
  f: MyFunc,
};

(4)函数

interface 也可以用来声明独立的函数。

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

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

(5)构造函数

interface 内部可以使用new关键字,表示构造函数。

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

interface 的继承

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

interface 继承 interface

interface 可以使用extends关键字,继承其他 interface。 interface 允许多重继承。

interface Style {
  color: string;
}

interface Shape {
  name: string;
}

interface Circle extends Style, Shape {
  radius: number;
}

如果子接口与父接口存在同名属性,那么子接口的属性会覆盖父接口的属性。注意,子接口与父接口的同名属性必须是类型兼容的,不能有冲突,否则会报错。

  id: string;
}

interface Bar extends Foo {
  id: number; // 报错
}

interface 继承 type

interface 可以继承type命令定义的对象类型。 如果type命令定义的类型不是对象,interface 就无法继承

interface 继承 class

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

接口合并

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

interface Document {
  foo: string;
}

document.foo = 'hello';

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

interface Cloner {
  clone(animal: Animal): Animal;
}

interface Cloner {
  clone(animal: Sheep): Sheep;
}

interface Cloner {
  clone(animal: Dog): Dog;
  clone(animal: Cat): Cat;
}

// 等同于
interface Cloner {
  clone(animal: Dog): Dog;
  clone(animal: Cat): Cat;
  clone(animal: Sheep): Sheep;
  clone(animal: Animal): Animal;
}

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

interface Circle {
  area: bigint;
}

interface Rectangle {
  area: number;
}

declare const s: Circle | Rectangle;

s.area;   // bigint | number

interface 与 type 的异同

首先表现在都能为对象类型起名。 interface 与 type 的区别有下面几点。

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

(2)interface可以继承其他类型,type不支持继承。

type Foo = { x: number };

interface Bar extends Foo {
  y: number;
}
interface Foo {
  x: number;
}

type Bar = Foo & { y: number };

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

(4)interface不能包含属性映射(mapping),type可以

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

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

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

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

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

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

(7)interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以。

type A = { /* ... */ };
type B = { /* ... */ };

type AorB = A | B;
type AorBwithName = AorB & {
  name: string
};

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