• 接口
    • 第一个接口
    • 可选属性
    • 函数类型
    • 数组类型
      • 实现一个接口
      • 类中的静态部分和实例部分
    • 继承接口
    • 混合类型

    接口

    TypeScript的一个核心守则就是专注于检查值的“形状(shape)”。在TypeScript中,接口承担起了这个职责。你可以使用它来命名类型,它是结构化代码的好工具,并且你也可以使用它来对外部调用代码进行约束。

    第一个接口

    一个简单的接口例子:

    1. function printLabel(labelledObj: {label: string}) {
    2. console.log(labelledObj.label);
    3. }
    4. var myObj = {size: 10, label: "Size 10 Object"};
    5. printLabel(myObj);

    TypeScript的类型检查器会去检查printLabel的调用。printLabel有一个参数,且这个参数必须是一个包含一个名为label的字符串属性的对象。值得注意的是,我们实际传入的对象,其实包含了额外的属性,但类型检查器仅仅只会去检查指定的属性是否正确存在。

    让我们重构一下以上代码,这次我们使用接口来描述一个必须包含一个名为label的字符串属性的对象:

    1. interface LabelledValue {
    2. label: string;
    3. }
    4. function printLabel(labelledObj: LabelledValue) {
    5. console.log(labelledObj.label);
    6. }
    7. var myObj = {size: 10, label: "Size 10 Object"};
    8. printLabel(myObj);

    接口LabelledValue与我们在上一个例子中所做的事是一样的。值得注意的是,我们并不需要像在其他的强类型语言中一样,传入printLabel的参数一定非得是一个实现了这个接口的对象。在TypeScript中,它们只需在形态上一致就可以了。只要我们传递的参数符合接口所列出的定义,它就是合法的。

    还有一个值得注意的点是,类型检查器并不会去关注接口中属性在被定义时的顺序。你可以以任意的顺序来实现它。

    可选属性

    一个接口中的所有属性并不都是必须的。在一些情况下,它们可能并不存在。比如,当用户传递一个option对象参数时,可能有很多配置属性都是可选的。

    可选属性例子:

    1. interface SquareConfig {
    2. color?: string;
    3. width?: number;
    4. }
    5. function createSquare(config: SquareConfig): {color: string; area: number} {
    6. var newSquare = {color: "white", area: 100};
    7. if (config.color) {
    8. newSquare.color = config.color;
    9. }
    10. if (config.width) {
    11. newSquare.area = config.width * config.width;
    12. }
    13. return newSquare;
    14. }
    15. var mySquare = createSquare({color: "black"});

    可选属性的写法和接口中其他的属性写法很像,仅仅在冒号之前添加一个?即可。

    可选属性的优势在于,我们即可以用它来描述可能出现的属性,同时又可以检查出那些不该存在的属性。如下面的例子中,我们在定义createSquare的函数体时,config的一个属性名拼错了,我们将会得到一个报错:

    1. interface SquareConfig {
    2. color?: string;
    3. width?: number;
    4. }
    5. function createSquare(config: SquareConfig): {color: string; area: number} {
    6. var newSquare = {color: "white", area: 100};
    7. if (config.color) {
    8. newSquare.color = config.collor; // Type-checker can catch the mistyped name here
    9. }
    10. if (config.width) {
    11. newSquare.area = config.width * config.width;
    12. }
    13. return newSquare;
    14. }
    15. var mySquare = createSquare({color: "black"});

    函数类型

    接口除了可以用来定义JavaScript对象中各式各样的属性。它也可以用来定义函数。

    在定义函数类型的接口时,语法很像函数声明,但是只有参数列表和返回值类型。

    1. interface SearchFunc {
    2. (source: string, subString: string): boolean;
    3. }

    一旦定义了函数接口,我们就可以像普通接口去使用它。下面的例子中,我们定义了一个函数接口,然后定义了一个函数实现了它:

    1. var mySearch: SearchFunc;
    2. mySearch = function(source: string, subString: string) {
    3. var result = source.search(subString);
    4. if (result == -1) {
    5. return false;
    6. }
    7. else {
    8. return true;
    9. }
    10. }

    在做函数接口的类型检查时,参数的名字是不必完全一样的,下面的例子也是完全合法的:

    1. var mySearch: SearchFunc;
    2. mySearch = function(src: string, sub: string) {
    3. var result = src.search(sub);
    4. if (result == -1) {
    5. return false;
    6. }
    7. else {
    8. return true;
    9. }
    10. }

    除了检查参数之外,函数的返回值也会被检查(这里是truefalse)。如果函数返回数字或字符串,那么类型检查器将会提示我们,函数的返回值与SearchFunc接口中所描述的不一致。

    数组类型

    既然我们可以用接口来描述函数,我们也可以用接口来描述数组。数组有一个“索引(index)”类型来描述这个数组的索引的类型,紧接着的是对应位置元素的返回值。

    1. interface StringArray {
    2. [index: number]: string;
    3. }
    4. var myArray: StringArray;
    5. myArray = ["Bob", "Fred"];

    TypeScript支持两种索引类型:字符串和数字。一个数组同时支持两种索引也是允许的,但是有一个限制,数字索引的返回值的类型,必须是字符串索引的返回值类型的子类型。

    尽管索引在描述数组和“字典(dictionary)”时很有用,但是它们也限制了返回值的类型。在下面的例子中,有属性没有符合索引中指定的返回值类型,所以会得到一个报错:

    1. interface Dictionary {
    2. [index: string]: string;
    3. length: number; // error, the type of 'length' is not a subtype of the indexer
    4. }

    实现一个接口

    接口在C#Java中的一大用处,就是给予一个类明确的限制。在TypeScript中,一样如此:

    1. interface ClockInterface {
    2. currentTime: Date;
    3. }
    4. class Clock implements ClockInterface {
    5. currentTime: Date;
    6. constructor(h: number, m: number) { }
    7. }

    你也可以在一个类的接口中定义一些方法,下面的例子中,我们定义了setTime方法:

    1. interface ClockInterface {
    2. currentTime: Date;
    3. setTime(d: Date);
    4. }
    5. class Clock implements ClockInterface {
    6. currentTime: Date;
    7. setTime(d: Date) {
    8. this.currentTime = d;
    9. }
    10. constructor(h: number, m: number) { }
    11. }

    接口仅仅用于定义一个类中的公有部分。用接口来定义类的私有属性/方法,都是不允许的。

    类中的静态部分和实例部分

    在使用类和接口时,你需要记住,一个类包含两部分:静态部分和实例部分。你可能会注意到,当你在一个接口中声明构造函数,并且尝试让一个类去实现这个接口时,你会得到一个错误:

    1. interface ClockInterface {
    2. new (hour: number, minute: number);
    3. }
    4. class Clock implements ClockInterface {
    5. currentTime: Date;
    6. constructor(h: number, m: number) { }
    7. }

    这是因为当类实现一个接口时,只有类的实例部分才会被检查。由于构造函数是类的静态部分,它将不会被检查。

    所以,你应当直接在类中处理静态部分:

    1. interface ClockStatic {
    2. new (hour: number, minute: number);
    3. }
    4. class Clock {
    5. currentTime: Date;
    6. constructor(h: number, m: number) { }
    7. }
    8. var cs: ClockStatic = Clock;
    9. var newClock = new cs(7, 30);

    继承接口

    和类一样,接口也可以相互继承。所以,你不需要在接口之间拷贝那些公有的属性了。它使你可以抽出接口之间的可重用部分:

    1. interface Shape {
    2. color: string;
    3. }
    4. interface Square extends Shape {
    5. sideLength: number;
    6. }
    7. var square = <Square>{};
    8. square.color = "blue";
    9. square.sideLength = 10;

    一个接口可以继承多个接口:

    1. interface Shape {
    2. color: string;
    3. }
    4. interface PenStroke {
    5. penWidth: number;
    6. }
    7. interface Square extends Shape, PenStroke {
    8. sideLength: number;
    9. }
    10. var square = <Square>{};
    11. square.color = "blue";
    12. square.sideLength = 10;
    13. square.penWidth = 5.0;

    混合类型

    正如我们之前提到的,接口可以描述JavaScript世界中的许多类型。但是因为JavaScript天生就是动态的,你们可能会遇到一些混合类型的对象。

    以下例子是一个即使函数类型又是对象类型的JavaScript对象:

    1. interface Counter {
    2. (start: number): string;
    3. interval: number;
    4. reset(): void;
    5. }
    6. var c: Counter;
    7. c(10);
    8. c.reset();
    9. c.interval = 5.0;

    当和第三方JavaScript库打交道时,你可能会用上上述特性,用以完整得描述这些库。