TypeScript系列教程七《接口》

2021-07-23 16:06:22 浏览数 (1)

TS 接口类型java接口类似,但是还是有一些差别的。

接口是什么


在生活里,我想买个水龙头,但是我不知道家里的管子多粗,我只需要给老板说买个普通的,回家去安装,发现正好。这里面就定义的接口的标准,通常水龙头接口和高压水管的接口都是固定的,这样外界才能更好的去配合标准工作。

还比如最近的5G标准,全世界协商。还有w3c 的JavaScript标准。想想一下浏览器如果没有标准多么可怕。这些标准都需要文件,协议去约束他们。

在编程世界里,我们用来约束和规定标准的结构定义叫做接口。

接口是最外面的一层,毫无疑问就外面看里面的第一印象,接口的设计的好坏,决定了对组件或者软件包的初始印象。

编程为什么需要接口


上面已经说出了接口的一个作用,那就是定义标准。

1、定义标准

接口的定义不需要现实,可以交由类去准守实现。在苹果的编程语言里,一直叫做协议。

编写协议的确是一个很费力的事情,但带来的收益再现实生活中往往都能感受的到。

2、编程抽象

类可以实现多个接口,这对于组合的概念和抽象事物的编程更加顺手。

TS中的接口(interface)


TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

鸭式辨型:像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子

对象接口

目标:定义一个鸭子的标准,检查什么是鸭子

1、鸭子在鸟的基础上有其他特征,先定义一个鸟的接口:

代码语言:javascript复制
interface Bird{
    //有两条腿
    leg:2 
    //温度恒温或者变化
    temperature:'const' | 'change'
    //是否有羽毛
    hasFeather:true
}

2、在鸟的基础上,定义鸭子接口

代码语言:javascript复制
interface Duck  extends Bird{
    //嘎嘎叫
    call:"gagaga"
    //会游泳
    swim:true
    //走路像鸭子
    runLike:"duck"
}

3、 检查鸡是不是鸭子类型

很明显的报错,鸡不是嘎嘎叫,不会游泳,走路不像鸭子,不属于鸭子标准所以错误。

这个例子也很好的抽象出了事物结构。

可选属性 和 只读属性

在使用TS接口或者类型定义的时候,我们经常会遇到一个属性只有在一种场景下需要传递,另一种场景不需要传递,但是不传还报错,如果必须传值弄的编程歧义过大。这时候可选属性出现了。

可选链式现代设计语言都会去设计的,配置类型安全去搭配使用。

下面的例子可以看出:

学生不需要去使用工作属性,那么这个属性应该可选。

代码语言:javascript复制
interface Person {
    study?:'poor' | 'hard'
    work?:'hard' | 'lazy'
}

let student:Person = {
    study:'hard'
}

只读属性不多赘述,只能读取不能set:

额外属性

有时候定义一个接口想的不是很全面,或者有预留的空间,这时候可以使用额外属性。

比如学生是人,人是可以跑的,但是不如我们所愿,因为我们没有给人定义跑的属性,这未免太死板:

我们可以使用额外属性扩充这个问题:

代码语言:javascript复制
interface Person {
    readonly study?:'poor' | 'hard'
    work?:'hard' | 'lazy'
    [propName: string]: any;
}

let student:Person = {
    study:'hard',
    run:true
}

函数接口

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

代码语言:javascript复制
interface Eat {
    (food:string):'hungry' | 'full'
}

interface Person {
    readonly study?:'poor' | 'hard'
    work?:'hard' | 'lazy'
    eat:Eat
    [propName: string]: any;
}

function studentEat(food:string) :'hungry' | 'full'{
    console.log(`学生去食堂吃${food}`);
    return 'full'
}

let studentEatArrow:Eat = (food:string) => {
    console.log(`学生去食堂吃${food}`);
    return 'full'
}

let student:Person = {
    study:'hard',
    eat:studentEat,
    run:true
}

函数类型检查不会检查参数名,参数个数和格式对应即可:

代码语言:javascript复制
let studentEatArrow:Eat = (nofood:string) => {
    console.log(`学生去食堂吃${nofood}`);
    return 'full'
}

let student:Person = {
    study:'hard',
    eat:studentEatArrow,
    run:true
}

可索引的类型

typescript 可以通过索引得到类型,下面我们自定义一个数组类型:

代码语言:javascript复制
interface MyArray<T> {
    [index:number] : T
}
let array:MyArray<string> = ["1","9"]

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

扩展使用,有些时候我们想通过key字符串取值,需要声明对象key类型:

代码语言:javascript复制
export interface StringElement {
  [key: string]: any;
}

export interface StringArrayMap {
  [key: string]: [string];
}

类类型

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

代码语言:javascript复制
interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(cDate:Date) { 
        this.currentTime = cDate
    }
}

你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime方法一样:

代码语言:javascript复制
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存在于类的静态部分,所以不在检查的范围内。

因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。

代码语言:javascript复制
interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick():void;
}

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

&nbpp;

继承接口

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

代码语言:javascript复制
interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

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

一个接口可以继承多个接口,创建出多个接口的合成接口。

代码语言:javascript复制
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;

&nbpp;

混合类型

先回到js,函数可以作为对象使用。

代码语言:javascript复制
var person = function () { };
person.xingming = "小明"
person.age = 19

console.log(person);

打印如下:

代码语言:javascript复制
[Function: person] { xingming: '小明', age: 19 }

在TS中混合类型:

代码语言: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;

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。 例:

代码语言:javascript复制
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 {

}

0 人点赞