接口

参考自ts官方文档

主要内容:
什么是接口?
接口的可选属性?、只读readonly
额外属性检查和如何绕过额外属性检查(断言、索引签名、等于一个新变量)
接口索引签名
函数接口、类的接口
接口的继承、接口继承类

1.接口是什么?

通过简单实例来看下接口是如何工作的?

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

let myObj = {size:10,label:"Size 10 Object"};
printLabel(myObj);

上述例子中是没有使用接口来描述的。
ts的类型检查器会查看printLabel调用。printLabel要求传入对象,其中需要有label属性名,值为string类型。

使用接口来描述,重写上面的例子

interface LabelledValue {
	label:string
}
//声明一个接口

function printLable(labelledObj: LabelledValue) {
	console.log(labelledObj.label);
}

let myObj = {size:10,label:"Size 10 Object"}
printLabel(myObj);

由上面得出:接口好比一个名字,对对象进行性描述,用于ts检查器调用检查。
需要注意的是:在接口传入的时候,我们只会关注值的外形,只要传入的对象满足上面提到的必要条件,那么他就是被允许的


2.接口的可选属性

如何定义接口中的属性是可选的,例子

interface SquareConfig {
	color?:string;
	width?:number;
}

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。
可选属性的好处之一是可以对存在的属性进行预定义,好处二,就是可以捕获,属性不存在时候的错误比如:

interface SquareConfig {
	color?:string;
	width?:number;
}

function createSquare(config:SquareConfig) : { color:string;area:number} {
	//....代码操作
	return {color:'white',area:100}
}

let mySquare = createSquare({color:"black"})

3.接口的只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性:

interface Point {
	readonly x:number;
	readonly y:number;
}

//赋值对象字面量钩子一个Point
let p1:Point = { x:10,y:20 }
//定义后只能读取不能修改
p1.x = 20 //error!

TypeScript具有ReadonlyArray类型,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

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!

可以使用断言重写,重写成普通数组后可以就修改数组

a = ro as number[];

readonly和const 区分
对象的属性用readonly,作为一个变量用const


4.额外属性检查

ts会对额外传入的属性进行检查,如果一个对象的字面量存在任何“目标类型”不包含是属性时,会发生错误

//接口
interface SquareConfig {
	color?:string;
	width?:number
}
//如果你在函数传入的参数有额外的属性 ts对额外的属性会进行检查 抛出错误
function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

注意传入createSquare的参数拼写为colour而不是color。 在JavaScript里,这会默默地失败。
绕开这些检查非常简单。 最简便的方法是使用类型断言:

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

然而最佳的方式是添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些作为特殊用途使用的额外属性。如果SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其他属性,我们接口可以这样写

interface SquareConfig {
	color?:string;
	width?:number;
	[propName:string]:any;
}

最后一种跳过额外属性检查的方式,就是讲对象赋值给一个变量:因为变量不会经过额外属性检查

//注意colour而不是color
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

要注意的是,你不应该经常去绕开这些检查,大部分额外属性检查错误是真正的bug。就是说如果遇到了额外类型检查出错的时候,你更应该重新审视自己声明的类型


5.函数类型

函数也是对象的一种,所以接口也可以表示函数类型
为了使用接口定义函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型函数定义

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

//使用定义的接口创建函数类型的变量,并将一个同类型的函数赋值给这个变量
let mySearch:SearchFun;
mySearch = function (source:string,subString:string) {
	let result = source.search(subString);
	return result > -1
}

对于函数类型的类型检查来说,函数的参数名不需要与接口定义的名字想匹配。上面的例子可以改写成如下形式

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

6.可索引的类型

接口也可以描述哪些“通过索引得到”的类型,比如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支持字符串和数组两种索引签名


7.类的接口

TypeScript中接口能够用来明确的强制一个类去符合某种契约

interface ClockInterface {
    currentTime: Date;
}

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

类的静态部分和实例部分:当你在操作接口去实现类的时候,你用构造函数签名器去定义这个类的时候会报错,这是因为constructor存在于类的静态部分,所以不在检查的范围内。

因此,对于类中的构造器函数,应该另行声明一个接口

interface ClockConstructor {
	new (hour:number,minute:number):ClockInterface;
	}

interface ClockInterface {
	tick();
	}
//声明一个函数,该函数用于使用ClockConstructor接口创建类的实例
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);

8.接口的继承

和类一样,接口也可以继承

interface Shape {
	color:string;
	}

interface Square extends Shape {
	sideLength: number;
}

let square = <Square>{};
//字面量对象断言为Square类型
square.color = "blue";
square.sideLength = 10;

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

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;