TS 核心原则之一就是对值所具有的结构进行类型检查。在 TS 里,接口的作用就是为这些类型命名,为代码或第三方代码定义契约。

  1. 接口初探
  2. 可选属性
  3. 只读属性
  4. 额外属性检查
  5. 函数类型
  6. 可索引的类型
  7. 类类型
  8. 继承接口
  9. 混合类型
  10. 接口继承类

1. 接口初探

  1. interface LabeledValue {
  2. label: string;
  3. }

2. 可选属性

  1. interface SquareConfig {
  2. color?: string;
  3. width?: number;
  4. }

3. 只读属性

只读属性创建后不能再次修改。

  1. interface Point {
  2. readonly x: number;
  3. readonly y: number;
  4. }
  5. let p1: Point = { x: 10, y: 20 };
  6. p1.x = 5; // error!

readonly vs const:对象作为变量时使用 const,作为属性时使用 readonly

4. 额外属性检查

可以漏写部分属性,但不能多写属性或者错写属性,即如果传入的对象中含有目标类型不存在的属性,TS 额外的属性检查机制会进行报错。

官方提供了绕开 TS 的额外属性检查的办法:

  1. // 1. 使用类型断言
  2. let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
  3. // 2. 声明接口时来一个任意类型,此时这个声明为该接口的对象,可以有任意数量、任意类型、只要名字不是 color 或 width 的属性
  4. interface SquareConfig {
  5. color?: string;
  6. width?: number;
  7. [propName: string]: any;
  8. }
  9. // 3. 更简单,只要把对象赋值到另一个变量上(抽离出来)即可:
  10. let squareOptions = { colour: "red", width: 100 };
  11. let mySquare = createSquare(squareOptions);

需要注意的是,只在某些复杂场景下才去考虑绕过额外属性检查,因为通常额外属性检查是用来发现潜在 bug 的。

5. 函数类型

接口能够用来描述一个 JavaScript 对象所能具有的各种样子(属性),另外也能用来描述函数的类型,其实就是接口中描述函数的签名。

  1. interface SearchFunc {
  2. (source: string, subString: string): boolean;
  3. }

名字不必一一对应,只要顺序对应上即可:

  1. let mySearch: SearchFunc;
  2. mySearch = function(src: string, sub: string): boolean {
  3. let result = src.search(sub);
  4. return result > -1;
  5. }

不写函数的类型也可以,TS 会自动类型推断:

  1. let mySearch: SearchFunc;
  2. mySearch = function(src, sub) {
  3. let result = src.search(sub);
  4. return result > -1;
  5. }

6. 可索引的类型

就像使用接口描述函数类型,我们也可以在接口中描述能够“通过索引得到”的类型,比如a[10]fullName["lastName"]。可索引类型有一个索引签名,描述了对象的索引类型和返回值类型。

TS 中的索引签名也有两种:number 和 string。

  1. interface StringArray {
  2. [index: number]: string;
  3. }
  4. let myArray: StringArray;
  5. myArray = ["Bob", "Fred"];
  6. let myStr: string = myArray[0];

接口中继续定义其他属性和类型的时候,要和索引签名所匹配:

  1. interface NumberDictionary {
  2. [index: string]: number;
  3. length: number; // 可以,length是number类型
  4. name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
  5. }

7. 类类型(Class Types)

类类型总算和 Java 里接口的基本作用一样了,在接口中定义的属性和方法,当一个类说自己符合某种接口契约时,该类就要至少实现这个接口中定义了的属性和方法,才能说自己是某种接口定义的类型:

  1. interface ClockTnterface {
  2. currentTime: Date; // 定义属性成员
  3. setTime(d: Date); // 定义方法成员
  4. }
  5. class Clock implements ClockInterface {
  6. currentTime: Date;
  7. setTime(d: Date) {
  8. this.currentTime = d;
  9. }
  10. constructor(h: number, m: number) { }
  11. }

类的类型分为:实例部分的类型和静态部分的类型,定义一个类时不能去直接实现(implements)一个构造器接口,因为当一个类实现了一个接口时,只对接口的实例部分进行类型检查,而构造器属于类的静态部分。

在定义一个工厂方法(用来生成某个类的实例)时,定义了构造器的类型,方法的签名定义了构造器的类型,当执行该工厂方法传入某个类时,会对传入的类中的构造函数进行构造器检查,看是否符合函数签名:

  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);

8. 继承接口

和类一样,接口也可以相互继承,而且还可以多根继承:

  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. // 使用类型断言强制转换为 Square 类型
  11. let square = {} as Square; // 或者这种写法 let square = <Square>{};
  12. square.color = 'blue';
  13. square.sideLength = 10;
  14. square.penWidth = 5.0;

9. 混合类型

得益于接口能够描述 JavaScript 类丰富的类型,因此混合类型使得一个对象可以同时作为函数和对象使用(JavaScript 中,函数其实也是对象):

  1. interface Counter {
  2. (start: number): string;
  3. interval: number;
  4. reset(): void;
  5. }
  6. function getCounter(): Counter {
  7. let counter = <Counter>function (start: number) { };
  8. counter.interval = 123;
  9. counter.reset = function () { };
  10. return counter;
  11. }
  12. let c = getCounter();
  13. c(10);
  14. c.reset();
  15. c.interval = 5.0;

10. 接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现,其它类实现该接口时也要同时继承那个类,不能直接去实现该接口。

使用场景并不是很多,可能当构建一个庞大的继承结构时会用到。

  1. class Control {
  2. private state: any;
  3. }
  4. interface SelectableControl extends Control {
  5. select(): void;
  6. }
  7. class Button extends Control implements SelectableControl {
  8. select() { }
  9. }
  10. class TextBox extends Control { }
  11. // Error: Property 'state' is missing in type 'Image'.
  12. class Image implements SelectableControl {
  13. select() { }
  14. }