可以在 TypeScript 中扩展类型吗?深入探讨性能与优化
你好,朋友!如果你曾经用 TypeScript 写过代码,那么你肯定遇到过这个问题:“能否像扩展接口一样扩展类型?”这个问题在 Stack Overflow 上获得了数千次浏览,这并非没有原因。看起来很简单:接口通过 extends 扩展,而类型通过交叉类型 (&) 扩展。但其中存在细微差别,这些差别直接影响代码的性能和可读性。
今天,我们将弄清楚如何在 TypeScript 中扩展类型,这与接口有何不同,以及如何在优化时避免犯错。开始吧!
Type vs Interface:永恒的较量
在讨论扩展之前,让我们先回顾一下它们的区别。接口是可以多次声明的“契约”(声明合并),而类型是任何形式的别名。两者都可以扩展,但方式不同。
如何扩展接口?
这部分大家都很熟悉:
interface Animal { name: string;}
interface Bear extends Animal { honey: boolean;}
const bear: Bear = { name: 'Winnie', honey: true };简单、优雅、有效。但是,如果我们想对类型做同样的事情呢?
如何扩展类型?
类型不直接支持 extends。而是使用交叉运算符 (&):
type Animal = { name: string;};
type Bear = Animal & { honey: boolean };
const bear: Bear = { name: 'Winnie', honey: true };看起来相似,但存在一些陷阱。让我们通过示例来探讨它们。
性能:哪个更快?
价值百万的问题:选择 interface extends 还是 type & 会影响编译和执行速度吗?剧透:会,但并非如你所想。
编译:接口胜出
TypeScript 编译接口比编译类型交叉更快。为什么?接口是“扁平”的结构,编译器可以缓存并线性处理。而类型交叉是复杂的结构,可能需要递归展开。
// 接口 - 快速interface A { a: string }interface B extends A { b: number }
// 类型 - 较慢type A = { a: string }type B = A & { b: number }在包含数百个类型的大型项目中,差异可能很明显。但如果你只有 10-20 个实体,你几乎感觉不到。
运行时:没有区别
TypeScript 是 JavaScript 的超集。在运行时,既没有接口也没有类型。它们只存在于开发阶段。因此,对程序执行速度完全没有影响。
什么时候类型比接口更好?
尽管编译速度可能较慢,但在某些场景下类型是不可替代的:
- 联合类型 — 类型的联合(例如,
string | number) - 条件类型 — 条件类型(例如,
T extends string ? 'yes' : 'no') - 映射类型 — 属性转换(例如,
{ [K in keyof T]: boolean })
接口无法处理这些。但如果你只需要扩展一个对象——接口是首选。
优化:如何避免搬起石头砸自己的脚
现在进入最有趣的部分。如何编写可扩展的类型,既不会拖慢编译速度,也不会让同事困惑?
规则 1:对象用接口,其他用类型
这是黄金标准。如果你描述一个对象的形状——使用 interface。如果你需要联合类型、交叉类型或泛型魔法——使用 type。
// 好interface User { id: number; name: string;}
type Status = 'active' | 'inactive';type Admin = User & { role: 'admin' };<