在 TypeScript 里,接口的作用就是为类型命名和为你的代码或第三方代码定义契约

一、接口初探

编译器只会检查那些必需的属性是否存在

  1. interface LabeledValue {
  2. label: string;
  3. }
  4. function printLabel(labeledObj: LabeledValue) {
  5. console.log(labeledObj.label);
  6. }
  7. let myObj = { size: 10, label: "Size 10 Object" };
  8. printLabel(myObj); // 可以,但是{ size: 10, label: "Size 10 Object" }直接放进函数里面,作为参数会报错

二、可选属性

格式:在可选属性名字定义的后面加一个 ? 符号
有点:
1,可以对可能存在的属性进行预定义
2,可以捕获引用了不存在的属性时的错误

  1. interface SquareConfig {
  2. color?: string;
  3. width?: number;
  4. }
  5. function createSquare(config: SquareConfig): { color: string; area: number } {
  6. let newSquare = { color: "white", area: 100 };
  7. if (config.clor) {
  8. newSquare.color = config.clor; // 错误,SquareConfig接口没有clor这个属性
  9. }
  10. if (config.width) {
  11. newSquare.area = config.width * config.width;
  12. }
  13. return newSquare;
  14. }
  15. let mySquare = createSquare({ color: "black" });

三、只读属性

定义:只能在对象刚刚创建的时候修改其值

  1. // 定义
  2. interface Point {
  3. readonly x: number;
  4. readonly y: number;
  5. }
  6. let p1: Point = { x: 10, y: 20 };
  7. p1.x = 5; // 错误

新增:ReadonlyArray 类型:它与 Array 相似,只是把所有可变方法去掉了

  1. let a: number[] = [1, 2, 3, 4];
  2. let ro: ReadonlyArray<number> = a;
  3. ro[0] = 12; // 错误
  4. ro.push(5); // 错误
  5. ro.length = 100; // 错误
  6. a = ro; // 错误
  7. a = ro as number[]; // 正确

ps):注意:readonly定义一个接口的属性,const定义一个变量

四、的属性检查

  1. interface SquareConfig {
  2. color?: string;
  3. width?: number;
  4. }
  5. function createSquare(config: SquareConfig){
  6. // ...
  7. }
  8. let mySquare = createSquare({ colour: "red", width: 100 }); // 直接作为参数会报错
  9. // 绕开检查的方法(尽量不要用)
  10. // 1,断言
  11. let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig); //
  12. // 2,字符串索引签名
  13. interface SquareConfig {
  14. color?: string;
  15. width?: number;
  16. [propName: string]: any; // 任意的值
  17. }
  18. // 3,将这个对象赋值给一个另一个变量: 因为 squareOptions 不会经过额外属性检查,所以编译器不会报错。
  19. let squareOptions = { colour: "red", width: 100 };
  20. let mySquare = createSquare(squareOptions);

五、函数类型

  1. // 函数类型申明
  2. interface SearchFunc {
  3. (source: string, subString: string): boolean;
  4. }
  5. // 函数类型使用
  6. let mySearch: SearchFunc;
  7. mySearch = function(source: string, subString: string) {
  8. let result = source.search(subString);
  9. return result > -1;
  10. };
  11. //正确 参数和申明不一样也可以
  12. mySearch = function(src: string, sub: string): boolean {
  13. //
  14. };
  15. // 错误,返回值不能不匹配
  16. mySearch = function(src, sub) {
  17. let result = src.search(sub);
  18. return "string";
  19. };

六、可索引的类型

通过索引得到的类型

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

Typescript 支持两种索引签名:字符串和数字。
同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。 也就是说用 100 (一个 number )去索引等同于使用 “100” (一个 string )去索引,因此两者需要保持一致。

  1. // 1,数字索引的返回值必须是字符串索引返回值类型的子类型
  2. class Animal {
  3. name: string;
  4. }
  5. class Dog extends Animal {
  6. breed: string;
  7. }
  8. interface NotOkay { // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
  9. [x: number]: Animal;
  10. [x: string]: Dog;
  11. }
  12. // 2,定义的其他类型,必须是索引类型的子类型
  13. interface NumberDictionary {
  14. [index: string]: number;
  15. length: number; // 可以,length是number类型
  16. name: string; // 错误,`name`的类型与索引类型返回值的类型不匹配
  17. }
  18. interface NumberOrStringDictionary {
  19. [index: string]: number | string;
  20. length: number; // 可以,返回值属于索引类型返回值的子类
  21. name: string; // 可以,返回值属于索引类型返回值的子类
  22. }
  23. // 3,只读索引
  24. interface ReadonlyStringArray {
  25. readonly [index: number]: string;
  26. }
  27. let myArray: ReadonlyStringArray = ["Alice", "Bob"];
  28. myArray[2] = "Mallory"; // 错误

七、类类型

1,实现接口

  1. interface ClockInterface {
  2. currentTime: Date;
  3. setTime(d: Date): void;
  4. }
  5. class Clock implements ClockInterface {
  6. currentTime: Date = new Date();
  7. setTime(d: Date) {
  8. this.currentTime = d;
  9. }
  10. constructor(h: number, m: number) {}
  11. }

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

当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor 存在于类的静态部分,所以不在检查的范围内。

  1. interface ClockConstructor { // 静态申明 和普通申明一样
  2. new (hour: number, minute: number);
  3. }
  4. interface ClockInterface { // 实现接口 用 implements 关键字 , 只对其实例部分进行类型检查
  5. tick();
  6. }
  7. const Clock: ClockConstructor = class Clock implements ClockInterface {
  8. constructor(h: number, m: number) {}
  9. tick() {
  10. console.log("beep beep");
  11. }
  12. };

八、继承接口

能够从一个接口里复制成员到另一个接口里

  1. interface Shape {
  2. color: string;
  3. }
  4. interface PenStroke {
  5. penWidth: number;
  6. }
  7. // 一个接口的继承
  8. interface Square1 extends Shape {
  9. sideLength: number;
  10. }
  11. let square1 = {} as Square1;
  12. square1.color = "blue";
  13. square1.sideLength = 10;
  14. // 多个接口的继承
  15. interface Square2 extends Shape, PenStroke {
  16. sideLength: number;
  17. }
  18. let square2 = {} as Square2;
  19. square2.color = "blue";
  20. square2.sideLength = 10;
  21. square2.penWidth = 5.0;

九、混合类型

  1. interface Counter {
  2. (start: number): string; // 函数本身
  3. interval: number; // 静态属性
  4. reset(): void; // 静态方法
  5. }
  6. function getCounter(): Counter {
  7. let counter = function(start: number) {} as Counter;
  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;

十、接口继承类

当你创建了一个接口,继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)
在 Control 类内部,是允许通过 SelectableControl 的实例来访问私有成员 state 的

  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. select() {}
  12. }
  13. class ImageControl implements SelectableControl {
  14. private state: any; // 错误继承
  15. select() {}
  16. }