在 TypeScript 中,type
和 interface
都可以用来定义对象类型,但它们之间有一些关键的区别,以及各自的使用场景和优势。
主要区别
扩展性
interface
可以被扩展和实现,并且可以在代码的任何地方被扩展,这对于声明合并(declaration merging)很有用。type
可以使用交叉类型(&
)来实现类似扩展的效果,但不能声明合并。
// Interface 扩展
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
let myDog: Dog = {
name: "Rex",
bark: () => { console.log("Woof!"); }
};
// Type 使用交叉类型实现"扩展"
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
bark(): void;
};
let myDogType: DogType = {
name: "Rex",
bark: () => { console.log("Woof!"); }
};
声明合并
interface
支持声明合并,即同名的interface
将会自动合并其成员。type
不支持声明合并。
// Interface 声明合并
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {
height: 5,
width: 6,
scale: 10
};
// Type 不支持声明合并,以下代码会报错
type BoxType = {
height: number;
width: number;
};
// 下面的声明将会产生一个错误,因为TypeScript不允许类型别名重复声明
type BoxType = {
scale: number;
};
使用联合和交叉类型
type
可以更方便地定义联合类型和交叉类型。interface
可以通过扩展多个接口来近似交叉类型,但不能定义联合类型。
// Type 可以定义联合类型
type StringOrNumber = string | number;
// 可以使用 StringOrNumber 类型的变量
let myVariable: StringOrNumber;
myVariable = "Hello"; // OK
myVariable = 42; // OK
// Interface 无法定义联合类型,但可以通过继承来近似交叉类型
interface StringOrNumberDescriptor {
toString(): string;
}
interface StringDescriptor extends StringOrNumberDescriptor {
value: string;
}
interface NumberDescriptor extends StringOrNumberDescriptor {
value: number;
}
计算属性和映射类型
type
可以使用计算属性和映射类型。interface
不能使用计算属性,但可以通过索引签名近似映射类型。
// Type 可以使用计算属性和映射类型
type Keys = "firstname" | "surname";
type DicMember = { [key in Keys]: string };
let member: DicMember = {
firstname: "John",
surname: "Doe"
};
// Interface 不支持计算属性,但可以通过索引签名来实现类似的功能
interface IndexSignature {
[key: string]: string;
}
let user: IndexSignature = {
firstname: "Jane",
surname: "Doe"
};
关于 import type
import type
是 TypeScript 3.8 引入的一个特性,它允许你只导入类型而不是值。这在某些情况下可以提高构建性能和避免意外的运行时导入。你可以使用 import type
来导入一个 interface
,因为在 TypeScript 中,interface
在编译后的 JavaScript 代码中是不存在的,它们仅仅是静态类型的一部分。
// 假设在一个文件中有如下interface
// File: AppCustomContext.ts
export interface AppCustomContext {
// ...
}
// 在另一个文件中,你可以这样导入它
// File: AppGlobal.ts
import type { AppCustomContext } from './AppCustomContext';
// 并定义一个新的interface
interface AppGlobal {
context: AppCustomContext;
}
在上面的例子中,AppCustomContext
被用作类型导入,然后在 AppGlobal
中作为属性 context
的类型。因为 AppCustomContext
是作为类型信息使用的,所以使用 import type
是合适的。
为什么使用 type
或 interface
- 如果你需要使用联合类型或元组类型,你应该使用
type
。 - 如果你想要利用声明合并或者你正在定义一个库的类型,
interface
是一个更好的选择。 - 如果你需要映射类型或是计算属性,你需要使用
type
。
实践中,在许多情况下,它们可以互换使用,但了解它们之间的差异可以帮助你做出更好的决定。