TypeScript手册翻译系列2-接口

接口

TypeScript的一个核心原则是类型检测重点放在值的形状(shape),这有时候被称为鸭子类型化(duck typing)或结构子类型化(structural subtyping)。在TypeScript中,用接口(interfaces)来命名这些类型,来定义项目内部代码的合约以及与外部代码的契约。typescript

第一个接口

理解interface如何工做,最容易的方式就是先看一个简单例子:
数组

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

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


当调用'printLabel'时类型检测器开始检查,'printLabel'函数有单个参数,要求传入的对象有一个类型为string,名为'label'的属性。注意这里传入的对象有多个属性,但编译器仅检测所须要的属性存在并且类型匹配便可。

能够重写上面的例子,但此次是用接口来描述要有一个类型为string,名为'label'的property

ide

interface LabelledValue {
  label: string;
}

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

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


interface 'LabelledValue'是描述前一个例子所须要的一个名字,它仍然表示要有一个类型为string,名为'label'的属性。注意没必要明确地给将这个接口的实现传递给'printLabel',这个与其余语言相似。这里重要的只是形状(shape)。若是传递给函数的对象知足列出的需求,那么就容许传入。

须要指出的是类型检测器不须要这些属性按照某种方式排序,只要接口所需的属性存在且类型匹配便可经过检测。

函数

可选属性

并不是须要一个接口中全部的属性(properties)。只有在特定条件下一些属性才存在,或者并不是存在全部的属性。当建立相似于"option bags"模式时,可选属性很广泛,传递给函数的对象只有部分属性被赋值。学习

下面是该模式的一个例子:
this

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

function createSquare(config: SquareConfig): {color: string; area: number} {  
  var newSquare = {color: "white", area: 100};  
  if (config.color) {
    newSquare.color = config.color;
  }  
  if (config.width) {
    newSquare.area = config.width * config.width;
  }  
  return newSquare;
}

var mySquare = createSquare({color: "black"});

有可选属性的接口在编码上与其余接口相似,每一个可选属性在属性声明时用一个 '?'来标记。
编码

可选属性的优势是能够描述可能存在的属性,同时对那些未填充的属性也会作类型检测。例如假定传递给'createSquare'的属性名称拼写错误,则会获得下面错误消息:
spa

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

function createSquare(config: SquareConfig): {color: string; area: number} {  
  var newSquare = {color: "white", area: 100};  
  if (config.color) {
    newSquare.color = config.collor;  // Type-checker can catch the mistyped name here
  }  
  if (config.width) {
    newSquare.area = config.width * config.width;
  }  
  return newSquare;
}

var mySquare = createSquare({color: "black"});

函数类型

接口能够描述JavaScript对象能够接受的各类各样的形状(Shape)。 除了描述带有属性的对象,接口还能够描述函数类型。
为了用接口描述函数类型,给接口一个调用标记(call signature),相似于只给出参数列表和返回值的一个函数声明。

.net

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


一旦定义,就能够像其余接口同样来使用该函数类型接口。下面展现如何建立一个函数类型变量,将相同类型的一个函数值赋值给它。
翻译

var mySearch: SearchFunc;

mySearch = function(source: string, subString: string) {  
  var result = source.search(subString);
  
  if (result == -1) {    
    return false;
  }  
  else {    
    return true;
  }
}


函数类型要可以经过类型检测,不须要参数名称保持一致。能够将上面的例子写为:

var mySearch: SearchFunc;

mySearch = function(src: string, sub: string) {  
  var result = src.search(sub);
  
  if (result == -1) {    
    return false;
  }  
  else {    
    return true;
  }
}

函数参数被依次一个一个检测,检测每一个参数位置对应的类型是否匹配。并且这里函数表达式的返回类型已经由返回值falsetrue暗示出。若是函数表达式返回的是numbers或strings,那么类型检测器将告警:返回类型与SearchFunc接口描述的返回类型不匹配。

数组类型

相似于如何利用接口来描述函数类型,接口也能够描述数组类型。数组类型有一个描述对象索引的'index'类型,以及访问索引对应的返回类型。

interface StringArray {
  [index: number]: string;
}

var myArray: StringArray;
myArray = ["Bob", "Fred"];

index能够有两种类型:string和number。能够同时支持两种index类型,但要求从numeric index返回的类型必须是从string index返回类型的子类型。

index标记功能的强大在于可描述数组和字典模式,还要求属性都要匹配索引返回类型。在下面例子中,属性没有匹配索引返回类型,所以类型检测器给出错误:

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

Class类型

实现接口

在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) { }
}


接口描述类的公开(Public)部分,而不包含私有部分。能够据此来检测类中也包含类实例私有部分的数据类型。

类的静态部分与实例部分之间的差别

当使用类与接口时,要注意有两种类型:静态类型部分与实例类型部分(the type of the static side and the type of the instance side)。若是建立一个有构造函数标记的接口,而后试图建立一个实现该接口的类时将获得错误:

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

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


这是由于当类实现一个接口时,只检测类的实例部分。因为构造函数是在静态部分,所以实例部分中没有包含构造函数,当检测时就报错
这时,须要在类中直接实现静态部分。在下面例子中直接使用类来实现静态部分:

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

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

var cs: ClockStatic = Clock;
var newClock = new cs(7, 30);

扩展接口

与类很类似的是interfaces能够扩展。这样就能够将一个接口中的成员拷贝到另外一个接口中,所以能够将接口划分为更细的可重用的组件:

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

var square = <Square>{};
square.color = "blue";
square.sideLength = 10;


一个接口能够扩展多个接口,将这些接口组合在一块儿:

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

var square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

前面提到,接口能够描述JavaScript中的许多类型。因为JavaScript语言的动态和灵活性,可能遇到一个对象是上面多个类型的组合体。
在下面例子中的对象包含一个函数类型,一个对象类型,以及一些属性:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

var c: Counter;
c(10);
c.reset();
c.interval = 5.0;


当与第三方JavaScript交互时,可能会用相似上面的模式来描述一个类型的完整形状(shape)。


翻译后记:

须要学习下 鸭子类型化(duck typing)、结构子类型化(structural subtyping)、"option bags"模式。

参考资料

[1] http://www.typescriptlang.org/Handbook#interfaces

[2] TypeScript - Interfaces, 破狼blog, http://greengerong.com/blog/2014/11/13/typescript-interfaces/

[3] TypeScript系列1-简介及版本新特性, http://my.oschina.net/1pei/blog/493012

[4] TypeScript手册翻译系列1-基础类型, http://my.oschina.net/1pei/blog/493181

相关文章
相关标签/搜索