TypeScript | 接口 Interface

450 阅读5分钟

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型,在声明一个对象、函数或者类时,先定义接口,确保其数据结构的一致性。TypeScript 编译器依赖接口用于类型检查。

Tips:

  • 定义接口要 首字母大写。
  • 只需要关注值的 外形,并不像其他语言一样,定义接口是为了实现。
  • 如果没有特殊声明,定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致。

接口的属性

可选属性

可选属性:在属性名后加 ? 符号

只读属性

只读属性:在属性名前加 readonly,做为 变量 用 const,若做为 属性 用 readonly

注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

任意属性

interface Person {
    name: string;
    age?: number;
    //使用 [propName: string] 定义了任意属性取 string 类型的值
    [propName: string]: any; 
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

函数类型

除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有 参数列表返回值类型 的函数定义。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;

mySearch = function(source: string, subString: string): boolean {
  return source.search(subString) > -1;
}

//函数的参数名不需要与接口里定义的名字相匹配。
//可改变函数的参数名,只要函数参数的位置不变

// source => src, subString => sub
mySearch = function(src: string, sub: string): boolean {
  return src.search(sub) > -1;
}

//不指定参数类型
mySearch = function(src, sub) {
  let result = src.search(sub);
  return result > -1;
}

可索引类型

索引签名是 number 类型,返回值是字符串类型:

interface ScenicInterface {
  [index: number]: string
}

let arr: ScenicInterface = ['西湖', '华山', '故宫']
let favorite: string = arr[0]

索引签名是 字符串类型。我们可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型:

// 正确
interface Foo {
  [index: string]: number;
  x: number;
  y: number;
}

// 错误
interface Bar {
  [index: string]: number;
  x: number;
  y: string; // Error: y 属性必须为 number 类型
}

类类型

我们希望类的实现必须遵循接口定义,那么可以使用 implements 关键字来确保兼容性。

这种类型的接口在传统面向对象语言中最为常见,比如 java 中接口就是这种类类型的接口。这种接口与抽象类比较相似,但是接口只能含有抽象方法和成员属性,实现类中必须实现接口中所有的抽象方法和成员属性。

//可以在接口中描述一个方法,在类里实现它
interface AnimalInterface {
  name: string
  eat(m: number): string
}

class Dog implements AnimalInterface {
  name: string;

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

  eat(m: number) {
    return `${this.name}吃肉${m}分钟`
  }
}

接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

继承接口

和类一样,接口也可以通过关键字 extents 相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

// 一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
// 继承了 Shape、PenStroke的属性
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

接口可以描述函数、对象的方法或者对象的属性。有时希望一个对象同时具有上面提到多种类型,比如一个对象可以当做函数使用,同时又具有属性和方法。

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

//getCounter() 函数,返回值是 Counter 类型
function getCounter(): Counter {
  let counter = function (start: number) { } as Counter;
  counter.interval = 123;
  counter.reset = function () { };
  return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

let counter = function (start: number) { } as Counter;-- 通过类型断言,将函数对象转换为 Counter 类型,转换后的对象不但实现了函数接口的描述,使之成为一个函数,还具有 interval 属性和 reset() 方法。断言成功的条件是,两个数据类型只要有一方可以赋值给另一方,这里函数类型数据不能赋值给接口类型的变量,因为它不具有 interval 属性和 reset() 方法。

学习链接