目录
TypeScript核心原则之一是对值所具有的结构进行类型检查。被称作“鸭式辫型法”或“结构性子类型化”。
- function printLable( lableObj: { lable:string} )
- {
- console.log( lableObj.lable );
- }
-
- let myObj = { size:10, lable:"Size 10 object" };
- printLable( myObj );
-
-
接口示例:
- interface LableValue{
- lable: string;
- }
-
- function printLable( lableObj: LableValue )
- {
- console.log( lableObj.lable );
- }
-
- let myObj = { size:10,lable:"size 10 object"};
-
- printLable( 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;
- }
- if(config.width){
- newSquare.area = config.width * config.width;
- }
- return new Square;
- }
-
- let mySquare = createSquare({color:"black"});
接口:
- //一些对象属性只能在对象刚创建的时候修改其值,可以在属性名前 用 readonly 来指定只读属性
-
- interface Point{
- readonly x: number;
- readonly y: number;
- }
-
- //可以通过赋值来构造一个Point,赋值后 x、y 再也不能被改变了。
- let p1:Point = { x:10, y:20 };
- p1.x = 5; //error
数组:
TypeScript具有ReadonlyArray
- let a:number[] = [1,2,3,4];
- let ro: ReadonlyArray<number> = a;
-
- ro[0]= 12; //error
- ro.push(5); //error
- ro.length = 100; //error
-
- a = ro; //error
-
- 上面的最后一行可以看到,把整个ReadonlyArray赋值给一个普通数组也是不可以的。
- 但是可以用’类型断言‘重写:
- a = ro as number[];
⚠️注意:readonly 与 const 的区别:
作为变量使用时用const, 作为属性使用时用readonly
我们在第一个例子里使用了接口,TypeScript让我们传入{size: number; lable: string}到仅期望得到{lable: string;} 的函数里。 我们也学过了可选属性。
然而,当你天真的将这两者结合的话就会像javaScript里那样搬起石头砸自己的脚。
- //拿createSquare例子来说
-
- interface SquareConfig{
- color?: string;
- width?: number;
- }
-
- function createSquare( config: SquareConfig ):{color: string, area: number}
- {
- //...
- }
-
- let mySquare = createSquare( {coor:"red", width: 100} );
-
- 注意⚠️:这里传的是coor 而不是 color, 在js中这会默默的失败。然而ts会认为这段代码可能存在bug.
-
- 对象字面量会被特殊对待 且 会经过额外属性检查,当它们赋值给变量或作为参数传递时,如果一个对象字面量存在任何‘目标类型’不包含的属性时,你会得到一个Error
- //error: 'coor' not expected in type 'SquareConfig'
- let mySquare = createSquare( {coor: "red", width: 100} );
-
- 想要绕开这些检查非常简单,最简单的方式就是使用类型断言:
- let mySquare = createSquare( {width:100, opacity:0.5} as SquareConfig );
然而最佳的避开方式是添加一个字符串索引签名,前提是你能够确定这个对象具有某些作为特殊用途的额外属性。
- //如果SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
-
- inteface SquareConfig{
- color?: string;
- width?: number;
- [propName: string]: any;
- }
-
- 这样SquareConfig就可以带有任意数量的属性,并且只要它们不是color和width ,那么就无所谓它们的类型是什么。
还有一种可以避开额外属性检查的方式:将这个对象赋值给另一个变量。
- let squareOption = { coor:"red", width:100 };
- let mySquare = createSquare(squareOption);
- 为了使用接口表示函数类型,我们需要给接口定义一个调用签名。参数列表里的每个参数都需要名字和类型
- 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)
- {
- let result = src.search(sub);
- return result > -1;
- }
函数的参数会逐个进行检查,要求对应位置上的参数类型上兼容的。如果你不想指定类型,TS的类型系统会根据返回值推断出参数类型。
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap["daniel"].
- //可索引类型具有一个 索引签名, 它描述了对象索引的类型。还有相应的索引返回值类型
- interface StringArray
- {
- [index:number]: string
- }
-
- let myArray: StringArray;
- myArray = ["Bob","Fred"];
- let myStr: string = myArray[0];
-
- 这里,我们定义了StringArray接口,它具有索引签名。
- 这个索引签名表示了当用 number去索引StringArray时会得到string类型的返回值
ts支持2种索引签名:字符串 和 数字。
可以同时使用2种类型的索引,但是数字索引的返回值必须是字符串索引返回值的子类型。
这是因为当使用number来索引时,js会将它转换成string 然后再去索引对象。
也就是说用 数字100 去索引 等同于用 字符串“100”去索引,因此两者要保持一致。
- //将索引签名设置为只读,这样就可以防止了给索引赋值:
- interface ReadonlyStringArray
- {
- readonly [index:number]:string;
- }
-
- let myArray:ReadonlyStringArray = ["Alice","Bob"];
- myArray[2] = "Mallory"; //error. 因为索引签名上只读的,所以不能设置
7.1.实现接口
- //1.与C#或java里的接口一样,ts也能用它来明确的强制一个类去符合某种契约。
- interface ClockInterface{
- currentTime: Date;
- }
-
- class Clock implements ClockInterface{
- currentTime: Date;
- constructor(h: number, m: number)
- {
- }
- }
-
- //2.也可以在接口中描述一个方法,在类里实现它。
- interface ClockInterface{
- currentTime: Date;
- setTime(d: Date);
- }
-
- class Clock implements ClockInterface
- {
- currentTime: Date;
- setTime(d: Date)
- {
- this.currentTime = d;
- }
- constructor(h:number, m:number){}
- }
7.2类静态部分与实例部分的区别
类具有2个类型: 静态部分的类型 和 实例的类型。
- 当一个类实现了一个接口时,只对其实例部分进行类型检查。
-
- 因此我们应该直接操作类的静态部分,
- 例:
-
- interface ClockConstructor
- {
- new (hour:number, minute:number): ClockInterface;
- }
-
- interface ClockInterface
- {
- tick();
- }
-
- function createClock(ctor:ClockConstructor, h: number, m: number ):ClockInterface
- {
- rerurn new ctor(h, m);
- }
-
- class DigitalClock implements ClockInterface
- {
- constructor(h:number, m: number){}
- tick(){
- console.log("beep beep");
- }
- }
-
- let digital = createClock(DigitalClock,7,32);
- //和类一样,接口也可以相互继承。
- interface Shape{
- color: string;
- }
-
- interface Square extends Shape{
- sideLength: number;
- }
-
- let square = <Square>{};
- square.color = "red";
- square.sideLength = 10;
-
-
- //一个接口也可以继承多个接口,创建出多个接口的合成接口。
- interface Shape{
- color: string;
- }
-
- interface PenStroke{
- penWidth: number;
- }
-
- interface Square extends Shape,PenStroke{
- sideLength: number;
- }
-
- let squ = <Square>{};
- squ.color = "blue";
- squ.penWidtg = 5.0;
- squ.sideLength = 10;
- 例: 一个对象可以同时作为函数和对象使用,并带有额外的属性。
-
- interface Counter{
- (start: number): string;
- interval: number;
- reset(): void;
- }
-
- function getCounter(): Counter
- {
- let cter = <Counter> function( start: number ){};
- cter.interval = 125;
- cter.reset = function(){};
- return cter;
- }
-
- let c = getCounter();
- c(10);
- c.reset();
- c.interval = 5.0;
-
- 在使用js第三方库时,你可能需要像上面这样去完整的定义类型
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。
接口同样会继承到类的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(){}
- }
-
- //error "Image"类型缺少“state”属性
- class Image implements SelectableControl
- {
- select(){}
- }
-
- 上面的例子中 selectableControl 包含了Control的所有成员,包括私有成员 state。
- 因为state是私有成员,所以只有Control的子类才能实现SelectableControl接口。