分不清TypeScript interface和type的区别,看这一篇

191 阅读3分钟

在 TypeScript 中,typeinterface 都可以用来定义对象类型,但它们之间有一些关键的区别,以及各自的使用场景和优势。

image.png

主要区别

扩展性

  • 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 是合适的。

为什么使用 typeinterface

  • 如果你需要使用联合类型或元组类型,你应该使用 type
  • 如果你想要利用声明合并或者你正在定义一个库的类型,interface 是一个更好的选择。
  • 如果你需要映射类型或是计算属性,你需要使用 type

实践中,在许多情况下,它们可以互换使用,但了解它们之间的差异可以帮助你做出更好的决定。