一.元组:表示是知道这个数组的数量和元素的类型的。这时就可以用元组。
二.接口初探:示例
function printLabel(labelledobj:{label:string}){//这个形参是个对象,并且这个对象有个label的属性,而且是字符串类型的 console.log(labelledobj.label)//这里打印了这个对象的label属性 } let myObj={size:10,label:'Size 10 Object'}//声明了一个对象,有两个属性size、label,label是个字符串 printLabel(myObj)//调用上面的函数把声明的对象传进去
// 下面我们重写上面的例子,这次使用接口来描述:必须包含一个label属性且类型为string: interface LabelledValue{//定义了一个接口,他的名称是LabelledValue label:string//里面必须要有一个label属性,label属性的类型必须是string }//可以说它是对下面的描述,就是必须要有label属性,并且是string类型,否则就会报错 function printLabell(labelledobj:LabelledValue){ console.log(labelledobj.label) } let myObj={size:10,label:'Size 10 Object'} printLabell(myObj)
//可选属性 interface LabelledValue{//定义了一个接口,他的名称是LabelledValue label?:string//在它属性后面加一个?就代表这个属性可选可不选 }//下面在调用的是就可以不用写这个label function printLabell(labelledobj:LabelledValue){ console.log(labelledobj.label) } let myObj={size:10,label:'Size 10 Object'}//但是这里的size这个属性,因为在上面的接口中并没有定义,所以 // 要在接口里写一下,否则它会报错,因为它不认识这个size,同时也可以给这个size后面加上? printLabell(myObj)
/额外的属性检查 // 我们在第一个例子里使用了接口,TypeScript让我们传入{ size: number; label: string; }到仅期望得到{ label: string; }的函数里。 我们已经学过了可选属性,并且知道他们在“option bags”模式里很有用。 // 然而,天真地将这两者结合的话就会像在JavaScript里那样搬起石头砸自己的脚。 比如,拿 createSquare例子来说: interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { // ... } let mySquare = createSquare({ colour: "red", 100 }); // 注意传入createSquare的参数拼写为colour而不是color。 在JavaScript里,这会默默地失败。 // 你可能会争辩这个程序已经正确地类型化了,因为width属性是兼容的,不存在color属性,而且额外的colour属性是无意义的。 // 然而,TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
//函数类型 // 为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。 interface SearchFunc {//定义一个函数接口 (source: string, subString: string): boolean; //参数列表的规则。属性名:属性值的类型。 //返回一个布尔值 ,希望它给我们返回一个true还是false } //示例 let mySearch: SearchFunc;//表达式的方法定义了一个函数 mySearch = function(source: string, subString: string) {//它的参数就是要有2个,必须是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; }
//可索引的类型 // 与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap["daniel"]。 // 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子: interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; 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; } // 错误:使用数值型的字符串索引,有时会得到完全不同的Animal! interface NotOkay { [x: number]: Animal; [x: string]: Dog; } // 字符串索引签名能够很好的描述dictionary模式,并且它们也会确保所有属性与其返回值类型相匹配。 // 因为字符串索引声明了 obj.property和obj["property"]两种形式都可以。 下面的例子里, name的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示: interface NumberDictionary { [index: string]: number; length: number; // 可以,length是number类型 name: string // 错误,`name`的类型与索引类型返回值的类型不匹配 } // 最后,你可以将索引签名设置为只读,这样就防止了给索引赋值: interface ReadonlyStringArray { readonly [index: number]: string; } let myArray: ReadonlyStringArray = ["Alice", "Bob"]; myArray[2] = "Mallory"; // error! // 你不能设置myArray[2],因为索引签名是只读的。
//类类型 // 实现接口 // 与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。 interface ClockInterface { currentTime: Date; } class Clock implements ClockInterface { currentTime: Date; constructor(h: number, m: number) { } } // 你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime方法一样: interface ClockInterface { currentTime: Date; setTime(d: Date); } class Clock implements ClockInterface { currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } } // 接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。 // 类静态部分与实例部分的区别 // 当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到, // 当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误: interface ClockConstructor { new (hour: number, minute: number); } class Clock implements ClockConstructor { currentTime: Date; constructor(h: number, m: number) { } } // 这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分, // 所以不在检查的范围内。 // 因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口, // ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock, // 它用传入的类型创建实例。 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是否符合构造函数签名。