接口
TypeScript 的核心原则之一就是对所有的结构进行类型检查, 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
TypeScript的接口就好像是一个名字,用来描述下面例子的要求,它代表了有一个label属性且类型为string的对象,它不会去检查属性的顺序,只要存在相应的属性并且类型是正确的就行了。
interface LabelledValue {
label: string
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
可选属性
label?: string
只读属性
readonly label: string
你可以通过赋值一个对象字面量来构造一个Point。 赋值后,x和y再也不能被改变了。
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
额外属性
如果SquareConfig带有类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
函数类型
除了描述带有属性的普通对象外,接口也可以描述函数类型。 函数类型的描述需要我们给接口定义一个调用签名,其实就是一个参数列表和返回值类型,参数列表中的每个属性都需要类型和名字
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 你也可以不指定类型,TypeScript的类型系统会推断出参数类型
可索引类型
可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
共有两种类型的索引签名:字符串和数字,可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用'string'索引,有时会得到Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
继承接口
接口和类一样,都可以相互继承,一个接口可以同时继承多个接口。
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;
类类型
实现接口
TypeScript能够用接口来明确的强制一个类去符合某种契约,你也可以在接口中描述一个方法,在类里实现它,如同下面的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) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。 一般在类中定义的方法都是公共方法,是可以在类的外部直接调用的,仅可在类的内部调用的方法即私有方法需要特殊定义,一般有两种定义方式。 1、将私有方法移出模块,因为模块内部的所有方法都是对外可见的。 2、利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
类静态部分与实例部分的区别
类有静态属性和实例属性两种属性,静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
如果想要检查类的静态部分,就直接操作静态部分,参考下面的例子:
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");
}
}
// 因为createClock第一个参数是ClockConstructor,在createClock(DigitalClock, 12, 17)会检查DigitalClock是否符合构造函数签名。
let digital = createClock(DigitalClock, 12, 17);