我想强调如何保持你的TypeScript代码的整洁和整齐。从本质上讲,这个系列的文章具有很强的观点性,应该用盐(这是复数)来看待。
在TypeScript中,有两种不同的方法来声明对象类型。接口和类型别名。多年来,这两种定义对象类型的方法都被写进了很多博客文章。而随着时间的推移,所有这些都变得过时了。现在,类型别名和接口之间已经没有什么区别了。而且*,*所有不同的东西都已经逐渐统一起来了。
在语法上,它们的区别是细微的。
type PersonAsType = {
name: string;
age: number;
address: string[];
greet(): string;
};
interface PersonAsInterface {
name: string;
age: number;
address: string[];
greet(): string;
}
它是一个等号。这种细微差别会对类型评估的时间产生一些影响--对类型别名来说是立即的,对接口来说是懒惰的--但仅此而已。你可以在同样的情况下使用接口和类型别名来做同样的事情。
- 在类的
implements声明中 - 作为对象字面的类型注解
- 用于递归类型结构
你的名字然而,有一个重要的区别,它可能会产生你通常不愿意处理的副作用。
声明合并#
接口允许声明合并,类型别名不允许。声明合并允许向一个接口添加属性,甚至在它被声明之后。
interface Person {
name: string;
}
interface Person {
age: number;
}
// Person is now { name: string; age: number; }
TypeScript本身在lib.d.ts 文件中经常使用这种技术,使得它可以根据ECMAScript的版本,直接添加新的JavaScript API的三角符号。如果你想扩展例如Window ,这是一个很好的功能,但在其他情况下,它可能会反击。以此为例。
// Some data we collect in a web form
interface FormData {
name: string;
age: number;
address: string[];
}
// A function that sends this data to a back-end
function send(data: FormData) {
console.log(data.entries()) // this compiles!! 😱
// but crashes horrendously in runtime 😕
}
哦,麻烦了,entries() 这个方法是怎么来的?这是一个DOM API!FormData 是浏览器API提供的接口之一,而且有很多。它们是全局可用的,没有什么能阻止你扩展这些接口。而且,如果你这样做,你也不会得到任何通知。
我们当然可以争论正确的命名,但是对于所有你让全局可用的接口来说,问题依然存在,也许是来自于一些依赖关系,你甚至不知道他们在全局空间中添加了这样的接口。
把这个接口改成一个类型别名,可以立即让你意识到这个问题。
type FormData = {
// ^ 💥 Duplicate identifier 'FormData'.(2300)
name: string;
age: number;
address: string[];
}
它还可以防止你的类型在不知不觉中被扩展。
索引访问类型#
声明合并也是接口不能作为索引访问类型的子集工作的原因。下面是一个向服务器发送数据的例子。你可以传入任何对象和一组HTTP头信息,要求所有键都是string ,所有值都是string 。
declare function
send(data: any, headers: Record<string, string>): void;
Record<string, string> 是与 相同的,这更好地显示了灵活的索引访问。{ [key: string]: string }
让我们为所需的HTTP头文件做两个类型的定义。一次是作为对象类型。
type HTTPHeaders = {
Accept: string,
Cookie: string
}
而另一个是作为一个接口。
interface HTTPHeaderInterface {
Accept: string,
Cookie: string,
}
如果你用一个已经被注释为HTTPHeaders 的对象来调用send ,一切都很美好。
const hdrs: HTTPHeaders = {
Accept: "text/html",
Cookie: ""
};
send({}, hdrs) // 👍
但是当你把hdrs 改为HTTPHeadersInterface 的时候,事情就会变得很糟糕。
const hdrs: HTTPHeaderInterface = {
Accept: "text/html",
Cookie: ""
};
send({}, hdrs)
// ^ 💥 Index signature is missing in type 'HTTPHeaderInterface'
TypeScript会抱怨索引签名丢失。只有当类型是最终的,像HTTPHeaders ,TypeScript才能正确地检查所有的属性和值是否可以分配给我们在send 中声明的Record<string, string> 类型。由于接口是为声明合并准备的,因此不是所有的属性都是已知的,TypeScript无法判断索引签名是否与Record<string, string> 。
这就是为什么我建议首选类型别名而不是接口。当然,如果你提供的库中的接口应该可以被其他人扩展,那么类型别名不会让你走得太远。但除此之外,类型别名是清晰、简单和整洁的。