自ES6开始,JavaScript中已经有了类,TypeScript中类存在的时间甚至更长。然而TypeScript中也有接口的概念,在代码中添加类型声明时,经常会出现这样的问题:
对于这种类型声明是使用接口还是使用类?
本文聚集TypeScript中接口和类概念比较,这样我们能很好地回答这个问题。
1.从示例开始
为了理解本文,下面先给一个示例:
fetch('https://test.com/foo/bar')
.then((response) => {
console.log(response.status) // Some HTTP status code, such as 200
})
1
2
3
4
这里只是示意很常见的一个任务————从远程服务器获取数据,然后进一步处理数据。
如何现在使用TypeScript处理上面代码,它会对response进行类型推断得到any类型。因为通过代码无法分析其应该属于何种类型。
为了增加程序类型安全,我们需要给response增加显示类型声明,请看下面代码告诉TypeScript编译器:
fetch('https://test.com/foo/bar')
.then((response : Response) => {
console.log(response.status) // Some HTTP status code, such as 200
})
1
2
3
4
现在再回想前面提到的问题,新的Response应该被定义为interface还是class?
2. 接口
TypeScript检查程序不同类型时,其中关键方法是使用“鸭子方法”。
如果看起来像鸭子,叫起来像鸭子,那就是一个鸭子
也就是说,我们决定什么能被类型化一种特定类型,主要看起是否有需要特性/结构/形状(后面就称为形状)。在TypeScript中,interface是用于给这种特定形状事物赋予名称的一种方法,后面在程序中可以通过名称引用它。
下面使用duck进行类比,使用接口进行定义:
// A duck must have...
interface Duck {
// `hasWings` property with the value `true` (boolean literal type)
hasWings: true
// `noOfFeet` property with the value `2` (number literal type)
noOfFeet: 2
// `quack` method which does not return anything
quack(): void
}
1
2
3
4
5
6
7
8
9
有了Duck类型,我们在代码中可以其定义duck实例(通过实现Duck接口):
// 可以通过编译
const duck: Duck = {
hasWings: true,
noOfFeet: 2,
quack() {
console.log('Quack!')
},
}
// 下面这句代码不能通过编译
const notADuck: Duck = {}
// The TypeScript compiler would tell us
// "Type '{}' is not assignable to type 'Duck'.
// Property 'hasWings' is missing in type '{}'."
1
2
3
4
5
6
7
8
9
10
11
12
13
14
接口可以帮助TypeScript在编译时捕获代码潜在问题,但有其还有更多重要特性:
接口仅用于编译时,编译后被即被删除,接口不会输出至最终的JavaScript代码中。
让我们通过接口来定义Response来完成本节:
interface Response {
status: number // Some HTTP status code, such as 200
}
fetch('https://test.com/foo/bar')
.then((response: Response) => {
console.log(response.status)
})
1
2
3
4
5
6
7
8
如果现在运行TypeScript编译器,不会有错误,当然环境中有fetch API,输出的JavaScript代码为:
fetch('https://test.com/foo/bar')
.then(function (response) {
console.log(response.status);
});
1
2
3
4
5
我们看到编译时定义的类型没有影响运行时程序,这就是TypeScript神奇的接口功能!
3. 类
现在我们重新使用类定义Response:
class Response {
status: number // Some HTTP status code, such as 200
}
fetch('https://test.com/foo/bar')
.then((response: Response) => {
console.log(response.status)
})
1
2
3
4
5
6
7
8
我们仅把interface改为class,这时TypeScript编译没有错误,保证了类型安全,实现效果一样。但真正不同的是编译后的JavaScript代码。与interface不同,class也是JavaScript的结构体,但其包括更多信息。最大的差异是类提供行为的实现,不仅仅是形状属性。
下面我们看TypeScript编译后的代码:
var Response = (function () {
function Response() {
}
return Response;
}());
fetch('https://test.com/foo/bar')
.then(function (response) {
console.log(response.status);
});
1
2
3
4
5
6
7
8
9
差异是类被编译成ES5函数形式,但我们并不需要这些代码。如果开发大型应用,使用重复使用这种模式声明类型,会导致应用中增加大量额外代码。
4. 总结
如果创建类型描述API的返回值,使用接口比较合适。与类不同,接口在编译后被完全删除,不会在JavaScript中增加不必要的代码。如果以后需要对接口的实现进行扩展,很容易将其转换成完整的类。
————————————————