Typescript 的一个核心原则是类型检查关注于值的‘类型’。有时被称作‘鸭子类型’(duck typing)或‘结构类型’(structural subtyping)。在typescript里,接口扮演了命名这些类型的角色,同时也是在你的代码和链接你的外部工程里定义链接的有力方法。
1、我们的第一个接口interface
查看接口如何工作的最简单的方法是开始一个简单的实例:
1
2
3
4
5
6
|
function printLabel(labelledObj: {label: string}) {
console.log(labelledObj.label);
}
var myObj = {size: 10, label: “Size 10 Object”};
printLabel(myObj);
|
类型检查将检查调用‘printLable’。‘printLable’函数有一个带有原型‘lable’类型为字符串string的对象参数。需要注意的是我们传入的对象实属性际要比此要多,但是最终编辑器只是检查符合参数要求的那个属性。
我们再写一次同样的例子,这一次使用接口来描述此函数需要一个字符串类型的属性‘lable’:
1
2
3
4
5
6
7
8
9
10
|
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
var myObj = {size: 10, label: “Size 10 Object”};
printLabel(myObj);
|
接口‘LabledValue’是一个可以用来描述我们前面例子函数需求的名称。它同样要求有一个属性叫做‘lable’并且类型是字符串string。需要注意的是我们不需要像其他语言那样明确的声明我们传入‘printLable’实例的对象。这里主要是‘类型’起到关键作用。如果传入函数的对象符合要求就行。
值得指出的是类型检查器不会要求这些属性按照一定的顺序,只要接口的要求的属性出现并且是正确的类型即可。
2、可选属性
不是接口的所有属性都是必须的。一些存在特定的条件或者可能根本不存在。这些可选的属性在选择袋(option bags),即传入一个函数的只有一个对象,在填补了几个属性时,这种方式很受欢迎。
这里以下面的模式为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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’函数里的属性名字打错啦,编译器将会通知我们一个错误消息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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”});
|
3、函数类型
接口也能够描述javascript对象包含的更宽范围的‘类型’。在添加一个对象的属性时,接口同样能描述函数的类型。
要用接口描述一个函数的类型,我们给这个接口一个调用签名。这是一个只有参数列表和返回值的函数声明。
1
2
3
|
interface SearchFunc {
(source: string, subString: string): boolean;
}
|
一但我们定义了之后就可以像其他接口一样使用函数类型的接口。这里,我们展示如何通过创建一个函数类型的变量然后设置他的函数值是同样的类型。
1
2
3
4
5
6
7
8
9
10
|
var mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
var result = source.search(subString);
if (result == -1) {
return false;
}
else {
return true;
}
}
|
对于函数类型的正确类型检查来说,参数的名字并不一定要一致。例如我们可以将上面的例子改写成下面这样:
1
2
3
4
5
6
7
8
9
10
|
var mySearch: SearchFunc;
mySearch = function(src: string, sub: string) {
var result = source.search(subString);
if (result == -1) {
return false;
}
else {
return true;
}
}
|
函数的参数检查时每个类型对应参数的位置互相检查。同样我们的函数表达式的返回类型暗示了它返回的值(这里是真或假)。若函数返回的是数字或字符串,类型检查其将通知我们SearchFunc返回值与接口不匹配。
4、数组类型Array Types
跟我们用接口来描述函数类型相似,我们也能描述数组类型。数组类型有一个‘index’类型用来描述允许索引的对象的类型和一个相应的返回类型来访问索引。
1
2
3
4
5
6
|
interface StringArray {
[index: number]: string;
}
var myArray: StringArray;
myArray = [“Bob”, “Fred”];
|
这里有两种索引支持类型,字符串和数字。可以同时支持两种索引形式,但约束数值类型的索引必须是字符类型索引返回类型的子类型。
索引签名是一种非常强有力的描述数组和‘dictionary’模式的方法,同时它也强制所有属性必须符合它的返回类型。在下面这个例子里,属性并没有符合通用索引,类型检查器将返回一个错误:
1
2
3
4
|
interface Dictionary {
[index: string]: string;
length: number; // error, the type of ‘length’ is not a subtype of the indexer
}
|
5、类类型class types
实现一个接口
在C#或Java等语言里一种最常用的接口是明确地限制一个类符合特定的要求,在typescript里也是可以实现的。
1
2
3
4
5
6
7
8
|
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
|
你也可以在一个接口中描述实现类中的方法,我们也用‘setTime’作为例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
|
接口描述类的公共方面而不是其私有方面。这将阻止你用它来检查一个类实例也有特殊的私有方面的类型。
6、静态/实例的类之间的区别
在处理类和接口时,它有助于记住一个类两种类型:静态的类型和实例的类型。您可能会注意到,如果你通过构造签名创建一个接口并尝试创建一个类实现该接口你会得到一个错误:
1
2
3
4
5
6
7
8
|
interface ClockInterface {
new (hour: number, minute: number);
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
|
这是因为当一个类使用接口时,只检查实例的类。由于构造函数是静态的将不会被检查。
然而,您将需要直接使用类“静态”的一面。在这个例子我们直接使用类:
1
2
3
4
5
6
7
8
9
10
11
|
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);
|
7、扩展接口
就像类,接口可以互相扩展。这个处理的任务复制一个接口的成员到另一个,在你单独的接口为可重用的组件时有更多的自由。
1
2
3
4
5
6
7
8
9
10
11
|
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
var square = <Square>{};
square.color = “blue”;
square.sideLength = 10;
|
一个接口可以扩展多个接口,创建一个所有组合的接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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;
|
8、混合型
正如我们前面所提到的,接口可以描述出现在现实世界JavaScript的丰富的类型。由于JavaScript的动态和灵活的性质,你偶尔会遇到一个对象,是上面描述的一些类型的组合。
例子是一个对象,作为一个函数和一个对象的附加属性:
1
2
3
4
5
6
7
8
9
10
|
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
var c: Counter;
c(10);
c.reset();
c.interval = 5.0;
|
与第三方JavaScript交互,您可能需要使用像上面完全描述的形状类型的模式。