微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Day 128/200 TypeScript 中的Interface接口用法

(一)定义

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。

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

我的理解是,用接受来处理比较复杂的类型检查。参数和函数类型都可以。

(二)类型

1、最简单的写法

1)没有接口时,这样书写;

function printLabel(labelledobj: { label: string }) {
  console.log(labelledobj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

2)改为接口的写法后,如下:

interface LabelledValue {
  label: string;
}

function printLabel(labelledobj: LabelledValue) {
  console.log(labelledobj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

我感觉这样会比较清晰的表示函数参数的类型。

也就是说,接口的作用之一是来检验函数参数类型的一个结构(写法)。

2、接口之描述参数

1)可选属性:意思是这个属性是可以不写的;

用法是接口里的属性后,冒号前加个?.

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

function createSquare(config: SquareConfig): {color: string; area: number} {
  let newSquare = {color: "white", area: 100};
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({color: "black"});

2)只读属性:一些对象属性只能在对象刚刚创建的时候修改其值。

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

3)例外:

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

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

如上,会出现报错。

TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误

官方这样解释,但我没看懂。

解决方案有两个:

第一,使用类型断言:

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

第二,使用接口额外参数定义:

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

3、接口之描述函数

基本用法

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

上面的方法等价于

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

好处就是,更明确的表示出函数的类型。

4、类类型

1)实现接口

与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

也可以先定义再实现

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

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

类是具有两个类型的:静态部分的类型和实例的类型;

其中,constructor存在于类的静态部分;

new是动态部分;

一个复杂一些的例子

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

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

5、继承接口

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

1)一个简单例子

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

2)一个接口可以继承多个接口

interface Shape {
    color: string;
}

interface Penstroke {
    penWidth: number;
}

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

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

6、混合类型

我的理解是,既可以作为函数的参数定义,函数体,或者继承,类类型等;

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;

7、接口继承类

class Control {
    private state: any;
}

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

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

class TextBox extends Control {
    select() { }
}

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}

class Location {

}

这里有疑问:

继承和接口有什么区别,在什么场景下使用?

存疑

extends是继承;

implements是接口;

父类和子类是通过接口实现的?还是通过继承实现的?

明显是通过继承实现的,也就是extends;

在上面的例子里,SelectableControl包含了Control的所有成员,包括私有成员state。 因为 state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有 Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。

Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上, SelectableControl接口和拥有select方法Control类是一样的。 ButtonTextBox类是SelectableControl的子类(因为它们都继承自Control并有select方法),但ImageLocation类并不是这样的。

可预见,Location 也会报错。

参考资料:

https://www.tslang.cn/docs/handbook/interfaces.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐