TypeScript学习笔记之接口类型

时间:2023-01-12 16:09:58

TypeScript的接口,个人理解就是一种约束,包括各种类型的契约或者代码定义上的契约。当然,和java中的用法基本一致,接口可以被继承也可以被实现。

定义一个简单的interface

interface LabelledValue {
  label: string;
}

function printLabel(labelledValue: LabelledValue) {
  console.log(labelledValue.label);
}

let myLabel: LabelledValue = {label: 'this is my label'}
printLabel(myLabel)   // this is my label
注意:
    1、可选属性:label?: string;
    2、只读属性:readonly label: string;(只允许创建的时候修改一次)
    
readonly和const:变量声明用const,属性声明用readonly

看一个readonly的例子:

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

let p1: Point = {x: 121, y: 78}
console.log(p1);    // {x: 121, y: 78}

p1.x = 22   // wrong! 
注意:
    如果是只读型数组,请使用ReadonlyArray

额外属性检查

任意数量属性

interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;    // 代表任意属性,其中key类型为string,value类型为any任意
}

函数类型接口

接口可以描述js对象中拥有的各种外形。比如如下可以描述js对象具有函数类型:

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

let mySearch: SearchFunc;
mySearch = function(src, sub) {     // ts会自动识别参数类型,参数名可以不一致
    let result = src.search(sub);
    return result > -1;
}

其中,source、subString均为string型入参,boolean类型为出参。

可索引的类型

接口描述这种类型,比较有意思,可索引的类型包含两种,一种是对象,一种的数组。可以在接口中描述索引的类型,以及索引值的类型,如下:

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

let z1: StringArray = {1: "awdawd"}
let z2: StringArray = ['admin']
console.log(z1, z2);    // { '1': 'awdawd' } [ 'admin' ]

如上接口,可以被数组实现,也可以被对象实现。索引签名除了number还要string,可以同时使用两种类型的索引。但是要注意:数字类型索引的索引值一定要是字符串索引类型索引值的子类型。举个例子:

class Animal {
  name: string;
}
class Dog extends Animal {
  breed: string;
}

// 错误:使用'string'索引,有时会得到Animal!
interface NotOkay {
  [x: number]: Animal;
  [x: string]: Dog;
}

说明,按照意思是number索引类型是Animal,string索引类型是Dog,Animal必须是Dog的子类型,但是实际恰恰相反——Dog是Animal类型的子类型,所以这里会报错。因为当使用number作索引时,js会将它转换为string,然后再去索引。所以必须保证number索引是string索引的子类型,否则会造成索引值混乱。

使用索引签名实现ReadonlyArray效果:

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

实现一个接口

在java里面,接口生来就是被实现的,就好像抽象对象生来就是要被继承一样。

interface ClockInterface {    // 定义接口,包含属性currentTime,所有该接口的实现必须包含该字段
  currentTime: Date;
}

class Clock implements ClockInterface{
  currentTime: Date;    // 该字段必须包含
  constructor(h: number, m: number){};
}

同样地,在接口里面可以再加函数约束,所有实现必须重写该函数:

interface ClockInterface {    // 定义接口,包含属性currentTime,所有该接口的实现必须包含该字段
  currentTime: Date;
  setTime(d: Date);
}

class Clock implements ClockInterface{
  currentTime: Date;    // 该字段必须包含
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number){};
}

继承一个接口

接口和类相似,彼此之间是可以相互继承的,使用关键字extend实现:

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};        // 断言,同:{} as Square
square.color = "blue";
square.sideLength = 10;     // { color: 'blue', sideLength: 10 }

这里,Shape接口被Square接口所继承,所以自然Square接口多了一个属性color。

混合类型

有时期望使用接口同时描述对象的属性、方法等,可以如下:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}
function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

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