之前有写过 TypeScript interface 的基本几种使用方式:

一、类类型

1、实现接口

TypeScript 中可以使用 Interface 去约束类的属性或者是方法,而这个类需要去实现这个接口。

比如下面声明了一个接口, currentTime 是 Date 类型:

interface ClockInterface {
    currentTime: Date;
}

而类可以去实现某个接口:

class Clock implements ClockInterface {
    setTime(d: number) { // 报错
        this.currentTime = d;
    }
    constructor(h: number, n: number) { 
    }
}

上面的代码 TypeScript 检查会报错误: Type 'number' is not assignable to type 'Date'.

而上面的 Class 因为实现了 ClockInterface 就必须拥有 currentTime 属性,因为要实现接口中的所有属性和方法,因此会报错如下: Class 'Clock' incorrectly implements interface 'ClockInterface'. Property 'currentTime' is missing in type 'Clock' but required in type 'ClockInterface'.

而类如果实现了 currentTime 属性,则不会报错:

class Clock implements ClockInterface {
    currentTime: Date
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, n: number) { 
    }
}

类类型实现接口能够保证类中属性的存在的确定性,TypeScript 只会检查类的公共属性,而不会检查类的私有属性或者是非公共属性。

2、类静态部分与实例部分的区别

如果使用 constructor 去定义一个接口,然后在类中实现这个接口:

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

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

上面会报错 Class 'Clock' incorrectly implements interface 'ClockConstructor'. Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'. 因为类实现了接口的时候,只对实例部分进行类型检查,而构造器 constructor 属于类的静态部分,所以不会去检查。

因此 new (hour: number, minute: number): any 本质上匹配不到任何可匹配类型。

二、继承接口

Interface 与 Class 一样,也是可以继承的,这对于代码重用来说很方便。

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

上面的接口 Square 继承了 Shape 接口,因此 Square 有两个属性,分别是 color: numbersideLength: number

而一个接口可以继承多个其他接口:

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

三、混合类型

所谓的混合类型,就是一个对象可以为函数和变量使用,这得益于 js 的高灵活性。

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;

上面 Counter 接口,既给了 getCounter(): Counter 方法服务,也服务于 counter 这个对象

一般混合类型在使用 JS 第三方库的时候,需要上面这种完整的定义类型。

四、接口继承类

比较有意思的是 TypeScript 中,接口可以反之继承类,会继承类的成员但是不会去实现。

虽然我自己是不推荐这种方式,但是对于其他类属性的抽离和其他类对其属性的复用上来说,有些情况下会很便捷。

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}

class Location {

}

上面的例子中 state 是 Control class 的一个私有成员 private 声明,而接口 SelectableControl 虽然继承了 Control,并且继承了 state 属性,但其他类如果要实现这个接口,必须是 Control class 的子类,因为接口 SelectableControl 继承了一个 pricate state 属性,因此只有子类才能去实现这个 state 私有成员。