TypeScript系列3-手册-接口

接口

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

时间: 2024-10-11 07:51:17

TypeScript系列3-手册-接口的相关文章

TypeScript系列6-手册-函数

函数 函数是JavaScript中任意应用程序的基本构件块.可以在函数基础上建立抽象层,模拟类,信息隐藏,模块.在TypeScript中,虽然已经有类和模块,但函数函数仍然扮演着如何做事的关键角色.TypeScript还在标准JavaScript 函数基础上增加了一些新的能力,来使得函数更容易使用. 函数 TypeScript与JavaScript一样,都可以创建命名函数,也可以创建匿名函数.这样允许开发人员根据应用程序选择最合适的方式,不论是在一个API中建立一列函数还是建立一个one-off

TypeScript系列4-手册-类

类 传统的JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件,但这对于OO编程人员来说显得比较笨拙,因为是在类的基础上来继承.从JavaScript标准ECMAScript 6开始可以采用面向对象基于类来构建应用.在TypeScript中开发人员现在就可以使用这些技术,TypeScript可以将它们编译为目前大多数浏览器和平台能允许的普通Javascript代码,可以不用等待下一版本的JavaScript的到来. 类 我们先看一个基于类的简单例子: class Greeter

almost最好的Vue + Typescript系列02 项目结构篇

基于vue-cli 3.x,配合typescript的环境构建的新vue项目,跟以前的结构相比,有了一些变化,下面我们来简单的了解一下 基本结构: node_modules: 项目中安装的依赖模块 public: 主页文件index.html && favicon.icon(将以往单独在外部的index.html移到了public文件夹下),index.html我们可以像平时普通的html文件一样引入文件(css,js)和书写基本信息,添加meta标签等. src: 源码文件夹,基本上我们

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)  (1)框架搭建    (2):数据库访问层的设计Demo 前言:这篇博客在数据访问层的基础上面我们继续学习对这个Demo的开发,希望大家都好好理解一下这个Demo的架构,到最后我实现权限的时候我就简单的说一下搭建过程就OK了,因为这个Demo的思想就是按照权限的设计方式去设计的,下面我们开始介绍面向接口的编程思想,如果感觉好的话可以

Cisco UCS C系列服务器配置手册之使用WebBIOS配置RDID

Cisco UCS C系列服务器配置手册 重启后在如下图5所示界面时按Ctrl+H进入图6的Raid配置界面. 图5 图6 进入到raid的配置界面,左面导航选择Configuration Wizard,进入图7. 图7 因为第一次做RAID,所以选择New configuration 创建新的raid配置,点击Next,进入图8. 图8 选择Manual Configuration手动安装,然后点击Next进入图9. 图9 将图9左侧的多个硬盘全部加入右侧到Drive Groups中(每选择一

Juniper-SSG系列之子接口(单臂路由)配置终结篇

子接口到底是什么东东,咋回事?我这里就过多的解释,如果不懂单臂路由,请自行"补功课",这样才会更容易理解SSG系列当中配置细节和问题. 说下需求,在常见的企业组网当中,不少有一些"不专业"的网工和网管做一些简单粗暴的组网,比如交换机全部当纯二层傻瓜使用,所有的网关均在出口设备上,在以往的接触当中和客户的改网项目经验中,其中发现不少有这类情况,所以在这里,完完全全的需要本着一个专业的态度聊SSG的部署. 好了,不多废话,直接上菜. 如下图: 子网需求: 分多个业务网段

Juniper-SSG系列之子接口(单臂路由)运用

先上图: 三.分析与预规划 规划如上图↑ 分析客户目前暂定的拓扑方案,实现多vlan间通信.G0/0/48端口做成Trunk,理论上SW-A默认只会让10.10.0.X/24的主机过,Juniper防火墙Ping vlanif1-6都能到,这个是问题来了,只有10.10.0.x/24的主机,端口不做情况下就能到Juniper设备上.这时就能意识到,单臂路由的方向!!(*^__^*) [单臂路由定义扫盲] 单臂路由(router-on-a-stick)是指在路由器的一个接口上通过配置子接口(或"逻

TypeScript学习笔记之接口类型

TypeScript的接口,个人理解就是一种约束,包括各种类型的契约或者代码定义上的契约.当然,和java中的用法基本一致,接口可以被继承也可以被实现. 定义一个简单的interface interface LabelledValue { label: string; } function printLabel(labelledValue: LabelledValue) { console.log(labelledValue.label); } let myLabel: LabelledValu

Go基础系列:Go接口

接口用法简介 接口(interface)是一种类型,用来定义行为(方法). type Namer interface { my_method1() my_method2(para) my_method3(para) return_type ... } 但这些行为不会在接口上直接实现,而是需要用户自定义的方法来实现.所以,在上面的Namer接口类型中的方法my_methodN都是没有实际方法体的,仅仅只是在接口Namer中存放这些方法的签名(签名 = 函数名+参数(类型)+返回值(类型)). 当用