TypeScript 文档学习

TypeScript 03 - 接口

2019-11-28  本文已影响0人  晓风残月1994

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

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

1. 接口初探

interface LabeledValue {
    label: string;
}

2. 可选属性

interface SquareConfig {
    color?: string;
    width?: number;
}

3. 只读属性

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

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

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

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

4. 额外属性检查

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

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

// 1. 使用类型断言
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

// 2. 声明接口时来一个任意类型,此时这个声明为该接口的对象,可以有任意数量、任意类型、只要名字不是 color 或 width 的属性
interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;
}

// 3. 更简单,只要把对象赋值到另一个变量上(抽离出来)即可:
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

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

5. 函数类型

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

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

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

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

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

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

6. 可索引的类型

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

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

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

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

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

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

7. 类类型(Class Types)

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

interface ClockTnterface {
  currentTime: Date; // 定义属性成员
  setTime(d: Date); // 定义方法成员
}

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

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

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

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

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

8. 继承接口

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

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

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

// 使用类型断言强制转换为 Square 类型
let square = {} as Square; // 或者这种写法 let square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
square.penWidth = 5.0;

9. 混合类型

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

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;

10. 接口继承类

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

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

class Control {
    private state: any;
}

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

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

class TextBox extends Control { }

// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
    select() { }
}
上一篇 下一篇

猜你喜欢

热点阅读