一.简介

interface 是对象的模板,可以看作是一种类型约定,中文译为“接口”。使用了某个模板的对象,就拥有了指定的类型结构。

如何定义一个接口:

如下面的代码所示,定义了一个接口Person,它指定一个对象模板,拥有三个属性nameage

adress。任何实现这个接口的对象,都必须部署这三个属性,并且必须符合规定的类型。

// 定义一个接口
interface Person {
  name: string;
  age: number;
  adress: string;
}

如何使用:

实现该接口很简单,只要指定它作为对象的类型即可。

// 定义一个接口
interface Person {
  name: string;
  age: number;
  adress: string;
}

// 与type的使用方式一致
let person: Person = {
  name: "Tom",
  age: 25,
  adress: "China",
};

方括号运算符可以取出 interface 某个属性的类型。

// 定义一个接口
interface Person {
  name: string;
  age: number;
  adress: string;
}
type x = Person["name"]; // 方括号运算符可以取出 name 属性的类型。
let p: x = "Tom";
console.log(typeof p); // string

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

  1. 对象属性
  2. 对象的属性索引
  3. 对象方法
  4. 函数
  5. 构造函数

1.对象属性

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

如果属性是可选的,就在属性名后面加一个问号。

interface Person {
  name: string;
  age: number;
  adress?: string; // 可选属性
}
let person: Person = {
  name: "Tom",
  age: 25,
};

如果属性是只读的,需要加上readonly修饰符。

interface Person {
  name: string;
  age: number;
  readonly adress: string; // 只读属性
}
let person: Person = {
  name: "Tom",
  age: 25,
  adress: "China",
};
person.adress = "USA"; // 编译器报错,只读属性不能被修改

2.对象属性索引(索引签名):

interface StringArray {
  [index: number]: string;
}

3.对象方法(方法签名):

interface Greeter {
  greet(message: string): void;
}

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

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

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

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

4.函数类型

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

5.构造函数类型

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

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

二.interface 的继承

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

1.interface 继承 interface

interface 可以使用extends关键字,继承其他 interface。

extends关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

如上面代码所示:Dog继承Animal,所以Dog有两个属性,一个是父接口,一个是子接口

interface 允许多重继承。

interface Style {
  color: string;
}

interface Shape {
  name: string;
}

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

上面示例中,Circle同时继承了StyleShape,所以拥有三个属性colornameradius

多重接口继承,实际上相当于多个父接口的合并

注意:

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

interface Foo {
  id: string;
}

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

(2).多重继承时,如果多个父接口存在同名属性,那么这些同名属性不能有类型冲突,否则会报错。

interface Foo {
  id: string;
}

interface Bar {
  id: number;
}

// 报错
interface Baz extends Foo, Bar {
  type: string;
}

2.interface 继承 type

type student = {
  id: number;
  name: string;
};
// 继承student接口,并且添加age属性
interface people extends student {
  age: number;
}

在上面的代码中,people继承student接口,并且添加age属性;

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

3.interface 继承 class

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

class Point {
  x: number = 0;
  y: number = 0;
}

interface Point3D extends Point {
  z: number;
}

三.接口合并

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

interface User {
  name: string;
}

interface User {
  age: number;
}

// 合并后等价于:
/*
interface User {
  name: string;
  age: number;
}
*/

为什么要这样设计:

这样的设计主要是为了兼容 JavaScript 的行为。JavaScript 开发者常常对全局对象或者外部库,添加自己的属性和方法。那么,只要使用 interface 给出这些自定义属性和方法的类型,就能自动跟原始的 interface 合并,使得扩展外部类型非常方便。

就比如下面的代码,Web 网页开发经常会对windows对象和document对象添加自定义属性,但是 TypeScript 会报错,因为原始定义没有这些属性。解决方法就是把自定义属性写成 interface,合并进原始定义。

interface Window {
  hello: string;
}

window.hello = "world";
console.log(window);
console.log(window.hello); // "world"

注意:

同名接口合并时,同一个属性如果有多个类型声明,彼此不能有类型冲突。

interface Person {
  id: number;
  name: string;
}
interface Person {
  id: string; // error: id should be number
}

四.interface 与 type 的异同

1.相同点

  • 都可以用来描述对象、函数、数组等结构类型。

    // 对象
    type Person = {
      name: string;
    };
    let person: Person = {
      name: "胖猫",
    };
    console.log(person.name); // 输出: 胖猫
    interface student {
      name: string;
    }
    let stu: student = {
      name: "ikun",
    };
    console.log(stu.name); // 输出: ikun
    
    // 数组
    type person_arr = Person[];
    let person_arr: person_arr = [
      {
        name: "Alice",
      },
      {
        name: "Bob",
      },
    ];
    console.log(person_arr[0].name); // 输出: Alice
    console.log(person_arr[1].name); // 输出: Bob
    interface student_arr {
      name: string;
    }
    let stu_arr: student_arr[] = [
      {
        name: "Jane",
      },
      {
        name: "Mike",
      },
    ];
    console.log(stu_arr[0].name); // 输出: Jane
    console.log(stu_arr[1].name); // 输出: Mike
    
    // 函数
    type add = (x: number, y: number) => number;
    let add: add = (x, y) => x + y;
    console.log(add(2, 3)); // 输出: 5
    interface add_interface {
      (x: number, y: number): number;
    }
    let add_interface: add_interface = (x, y) => x + y;
    console.log(add_interface(2, 3)); // 输出: 5
    
    // .......
    
  • 都可以通过扩展组合多个类型。(interface自动合并,type需要&操作)

    interface Person {
      name: string;
    }
    interface Person {
      age: number;
    }
    let person: Person = {
      name: "Alice",
      age: 25,
    };
    
    type Person2 = {
      name: string;
    } & {
      age: number;
    };
    let person2: Person2 = {
      name: "Bob",
      age: 30,
    };
    

2.不同点

(1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。
(2)interface可以继承其他类型,type不支持继承。

继承的主要作用是添加属性,type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。

(3)同名interface会自动合并,同名type则会报错。也就是说,TypeScript 不允许使用type多次定义同一个类型。

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

(4)interface不能包含属性映射(mapping),type可以
(5)this关键字只能用于interface
(6)type 可以扩展原始数据类型,interface 不行。
(7)interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以。

下面的这个图片来自官网:

差异点 interface type
是否能表示非对象类型 不能  可以(如联合、原始类型等)
是否支持继承  支持继承(extends)  不直接支持继承
同名定义合并 会合并  会报错
属性映射(Mapped Types)  不支持  支持
this 类型使用  支持  不支持
扩展原始类型  不行  可以
表达复杂类型  限制较多  更灵活(支持交叉、联合等)

五.总结

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

注意:使用带有 extends 通常可以提高编译器的性能 的接口,而不是带有交叉的类型别名

下一篇:TypeScript---(值类型 | 联合类型 | 交叉类型)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐