码迷,mamicode.com
首页 > 其他好文 > 详细

TypeScript系列3-手册-接口

时间:2015-08-17 12:23:07      阅读:379      评论:0      收藏:0      [点我收藏+]

标签:

接口

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

第一个接口

理解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

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"模式时,可选属性很普遍,传递给函数的对象只有部分属性被赋值。

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

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‘的属性名称拼写错误,则会得到下面错误消息:

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),类似于只给出参数列表和返回值的一个函数声明。

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系列2-手册-基础类型, http://my.oschina.net/1pei/blog/493181

TypeScript系列3-手册-接口

标签:

原文地址:http://my.oschina.net/1pei/blog/493388

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!