TypeScript 03 - 接口

作者: 晓风残月1994 | 来源:发表于2019-11-28 00:22 被阅读0次

    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() { }
    }
    

    相关文章

      网友评论

        本文标题:TypeScript 03 - 接口

        本文链接:https://www.haomeiwen.com/subject/zwjmwctx.html