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<T>,与Array<T>相似,使数组不能修改
let a:number[]=[1,2,3,4]
let ro:ReadonlyArray<number>=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>{}
square.color="Blue"
square.sideLength=23

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

interface Share2{
    color:string;
}
interface PenStroke2{
    penWidth:number;
}
interface Square2 extends Share2,PenStroke2{
    sideLength:number;
}
let square2 = <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 = <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类并不是这样的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容