TypeScript学习之路——(2)Interfaces(接口)

TypeScript v3.1

//一个简单的事例看看接口是如何工作的

function printLabel(labelledObj:{label:string}){
    console.log(labelledObj.label)
 }
 let myObj = {size:10,label:"size 10 object"}
 printLabel(myObj)

//利用interface重写,要求:参数必须包含一个label的属性并且类型为string

interface labelledValue{
    label:string
}

function printLabel(labelledObj:labelledValue){
    console.log(labelledObj.label)
}

let myObj = {size:10,label:"size 10 object"}
printLabel(myObj)
可选属性

好处:
1是可以对可能存在的属性进行预定义
2是可以捕获引用了不存在的属性的错误

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
    }
    // // Error: Property 'clor' does not exist on type 'SquareConfig'
    // if(config.clor){
    //     newSquare.color = config.clor
    // }
    if(config.width){
        newSquare.area = config.width*config.width
    }
    return newSquare
}
let mySquare = createSquare({color:"black"})
console.log(mySquare)
只读属性
interface Point{
    readonly x:number,
    readonly y:number
}
let pl:Point={x:10,y:30}
// pl.x=5 //Error x,y都不能修改
//TypeScreipt具有ReadonlyArray,与Array相似,使数组不能修改
let a:number[]=[1,2,3,4]
let ro:ReadonlyArray=a
// ro[0]=12 //Error
// ro.push(3)//Error
// ro.length=66 //Error
// a=ro //Error

//虽然把整个ReadonlyArray复制到一个普通的数组不行,但是可以用类型断言重写

a= ro as number[]
额外的类型检查
// let mySquare2 = createSquare({ colour: "red", width: 100 });// error: 'colour' not expected in type 'SquareConfig'

//绕开这些检查非常简单。 最简便的方法是使用类型断言:

let mySquare2 = createSquare({width:100,option:0.5} as SquareConfig)

//然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
//在这我们要表示的是SquareConfig可以有任意数量的属性,并且只要它们不是color和width,那么就无所谓它们的类型是什么。

interface SquareConfig2{
    color?:string,
    width?:number,
    [propName:string]:any
}

//还有一种跳过检查的方式,就是利用变量传入函数

let squareOptions = {colour:"red",width:100}
let mySquare3 = createSquare(squareOptions)
函数类型

//接口可以描述函数类型
//未来使用接口表示函数类型,我们需要给接口定义一个调用签名,它就像一个职业参数列表和返回值类型的函数定义,参数列表里的每个参数都需要名字和类型

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

//这样定义后,我们可以向使用其它接口一样使用这个函数类型的接口
//eg:如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量

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

//对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
//比如,我们重构一下上面的例子

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

//函数的参数会逐个进行检查,要求对于位置上的参数类型是兼容的
//如果不想指定类型,typescript的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc类型变量
//函数的返回值类型是通过其返回值推断出来的,如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc接口中定义的不匹配

let mySearch3:SearchFunc
mySearch3 = function(src,sub){
    let result = src.search(sub)
    return result >-1
}
可索引的类型

//与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到的类型”,比如a[2]或者hh['as']。
//可索引类型具有一个索引类型签名,它描述了对象索引的类型,还有相应索引返回值类型,eg:

interface StringArray{
    [index:number]:string
}
let myArray:StringArray
myArray = ["Bob","LiHua"]
let myStr:string = myArray[0]

//上面定义了StringArray接口,它具有索引签名。
//这个索引签名表示当用number去索引StringArray时会得到string类型的返回值

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

class Animal{
    name:string
}
class Dog extends Animal{
    breed:string
}
//Error
// interface NotOkay{
//     [x:number]:Animal;//数字索引类型“Animal”不能赋给字符串索引类型“Dog”。
//     [x:string]:Dog;
// }

//这才是正确的
interface NotOkay{
    [x:number]:Animal;
    [x:string]:Animal;
}

//字符串索引签名能够很好的描述dictionary模式,并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了 obj.property和obj["property"]两种形式都可以。

interface NumberDictionary{
    [index:string]:number;
    length:number;//ok,length是number类型
    // name:string //类型“string”的属性“name”不能赋给字符串索引类型“number”。
}

//索引签名只读

interface ReadonlyStringArray{
    readonly [index:number]:string
}
let myArray1:ReadonlyStringArray=["AA","BB"]
// myArray1[2] = "CCC"//Error 类型“ReadonlyStringArray”中的索引签名仅允许读取
类类型

//实现接口,ts能够用接口来明确强制一个类去符合某种契约

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

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

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

//接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

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

//当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

//Error
// interface ClockConstructor{
//     new(h:number,m:number)
// }
// class Clock3 implements ClockConstructor{
//     currentTime:Date;
//     constructor(h:number,m:number)
// }

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

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

interface ClockConstructor{
    new(hour:number,minute:number):ClockInterface3
}
interface ClockInterface3{
    tick()
}
function createClock(ctor:ClockConstructor,hour:number,minute:number):ClockInterface3{
    return new ctor(hour,minute)
}
class DigitalClock implements ClockInterface3{
    constructor(h:number,m:number){}
    tick(){
        console.log("this is tick")
    }
}
class AnalogClock implements ClockInterface3{
    constructor(h:number,m:number){}
    tick(){
        console.log("this is third tick")
    }
}
let digital = createClock(DigitalClock,12,15)
let analog = createClock(AnalogClock,3,45)

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

继承接口

//接口可以互相继承,从一个接口里复制成员到另外一个接口里,可以灵活地将接口分割到可重用的模块里

interface Share{
    color:string;
}
interface Square extends Share{
    sideLength:number;
}
let square = {}
square.color="Blue"
square.sideLength=23

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

interface Share2{
    color:string;
}
interface PenStroke2{
    penWidth:number;
}
interface Square2 extends Share2,PenStroke2{
    sideLength:number;
}
let square2 = {}
square2.color="Red"
square2.sideLength=10
square2.penWidth=5.0
混合类型

//一个对象可以同时为函数和对象使用,并且带有额外属性

interface Counter{
    (start:number):string;
    interval:number;
    reset():void;
}
function getCounter():Counter{
    let counter = function(start:number){};
    counter.interval=123;
    counter.reset = function(){}
    return counter
}
let c4 = getCounter()
c4(12)
c4.reset()
c4.interval = 3.4

//在使用JavaScript第三方库时,可以许愿像上面那样完整地定义类型

接口继承类

//当接口继承了一个类类型时,它会继承类的成员但不包括其实现
// 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。

// 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

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

class Control{
    private state:any;
}
interface SelectableControl extends Control{
    select():void;
}
class Button extends Control implements SelectableControl{
    select(){}
}
class TextBox extends Control{
    select(){}
}

//类“Image2”错误实现接口“SelectableControl”。Property 'state' is missing in type 'Image2' but required in type 'SelectableControl'
// class Image2 implements SelectableControl{
//     select(){}
// }
class Location2 {

}

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

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

你可能感兴趣的:(TypeScript学习之路——(2)Interfaces(接口))