1. 类实现接口

与C#或Java里接口的基本作用一样,TS也能够用它来明确的强制一个类去符合某种 契约 。我们也是用implements 关键字来实现一个接口:

  1. interface UserInterface {
  2. username: string;
  3. getName(),
  4. setName(username: string);
  5. }
  6. class User implements UserInterface {
  7. username: string;
  8. getName() {
  9. return this.username
  10. }
  11. setName(username: string) {
  12. this.username = username
  13. }
  14. constructor(username: string) { }
  15. }

实现接口,包括实现接口中的属性和方法,上例中 setNaem 就是一个需要实现的方法。
接口描述了类的公共部分,而不包括私有部分。 它不会帮你检查类是否具有某些私有成员。

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

这一部分在官网说的很绕,简单来说就是:类对接口的实现就是在说明这个类的实例会有什么。也就是说当对象被 new 出来之后会有什么。而不是说这个类class里会有什么。
其中 class内的部分就是类静态部分new 出来之后的实例就是实例部分。检查器只对实例部分进行检查,不会检查静态部分。(虽然错误会直接反应在类定义处)
所以,当我们在接口中定义构造函数签名(也就是 new 方法) 并且用类去实现它的时候,检查器会始终报错,因为类的实例中,不可能有 new 方法(或构造方法),而接口中却说实例里会有 new 方法(恼),这就产生了矛盾,所以报错:

  1. interface UserConstructor {
  2. // constructor(): void // 一个构造器签名
  3. new(username: string) // 一个构造器签名
  4. }
  5. // 以下,始终错误
  6. class UserInfo implements UserConstructor {
  7. constructor() { }
  8. }

image.png
所以,如果一个接口中定义了构造器签名,那么就不要直接用类去实现它。

解决方法

如果你执意要定义构造器类型的接口,那么你就不应该直接用类去实现它,而是作为一个变量,让变量调用new方法来返回一个实例:

  1. interface UserConstructor {
  2. // constructor(): void // 一个构造器签名
  3. new(username: string): UserInfo // 一个构造器签名
  4. }
  5. class UserInfo {
  6. constructor(public username: string) {
  7. this.username = username
  8. }
  9. }
  10. let userConstruct: UserConstructor = UserInfo // 类类型是静态的,类型检查不会生效,所以将类类型赋值给接口类型也不会报错
  11. let user = new userConstruct("ricky")

或者,你也可以将其封装为一个可复用的方法:

  1. interface ClockConstructor {
  2. new (hour: number, minute: number): ClockInterface;
  3. }
  4. interface ClockInterface {
  5. tick();
  6. }
  7. function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
  8. return new ctor(hour, minute);
  9. }
  10. class DigitalClock implements ClockInterface {
  11. constructor(h: number, m: number) { }
  12. tick() {
  13. console.log("beep beep");
  14. }
  15. }
  16. class AnalogClock implements ClockInterface {
  17. constructor(h: number, m: number) { }
  18. tick() {
  19. console.log("tick tock");
  20. }
  21. }
  22. let digital = createClock(DigitalClock, 12, 17);
  23. let analog = createClock(AnalogClock, 7, 32);

因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名。

2. 接口继承接口

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

  1. interface Shape {
  2. color: string;
  3. }
  4. interface Square extends Shape {
  5. sideLength: number;
  6. }
  7. let square = <Square>{};
  8. square.color = "blue";
  9. square.sideLength = 10;

一个接口可以继承多个接口,创建出多个接口的合成接口。

  1. interface Shape {
  2. color: string;
  3. }
  4. interface PenStroke {
  5. penWidth: number;
  6. }
  7. interface Square extends Shape, PenStroke {
  8. sideLength: number;
  9. }
  10. let square = <Square>{};
  11. square.color = "blue";
  12. square.sideLength = 10;
  13. square.penWidth = 5.0;

3.接口继承类

是的,你没有看错,TM接口还能继承类了,接口不是更抽象的类吗?竟然还tm能继承类的。(吐槽)
介绍
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement):

  1. class Control {
  2. private state: any;
  3. }
  4. // 接口继承类
  5. interface SelectableControl extends Control {
  6. select(): void;
  7. }
  8. // 子类,可以实现接口
  9. class Button extends Control implements SelectableControl {
  10. select() { }
  11. }
  12. // 错误:不是子类,“Image”类型缺少“state”属性。
  13. class Image implements SelectableControl {
  14. select() { }
  15. }

当然,如果类中没有私有或受保护的成员变量,那么该接口可以被任意实现。

  1. class Control {
  2. public state: any;
  3. }
  4. // 接口继承类
  5. interface SelectableControl extends Control {
  6. select(): void;
  7. }
  8. // 非子类也能实现没有继承到私有或受保护成员的接口
  9. class Image implements SelectableControl {
  10. select() { }
  11. }