TypeScript日常类型(7): Type Aliases 和 Interfaces

117 阅读4分钟

类型别名(Type Aliases)

我们之前通过直接在类型注解中编写对象类型和联合类型来使用它们。这很方便,但通常我们希望多次使用相同的类型,并通过单一的名称引用它。

类型别名就是这种功能——为任何类型定义一个名称。类型别名的语法是:

type Point = {
  x: number;
  y: number;
};

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

你实际上可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以为联合类型命名:

type ID = number | string;

请注意,类型别名只是别名 —— 你不能用类型别名来创建同一类型的不同/独立“版本”。当你使用类型别名时,它就像你直接写了别名的类型一样。换句话说,这段代码可能看起来不合法,但根据 TypeScript 的规定是合法的,因为这两种类型是同一个类型的别名:

declare function getInput(): string;
declare function sanitize(str: string): string;

type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}

// 创建一个已清理的输入
let userInput = sanitizeInput(getInput());

// 仍然可以用字符串重新赋值
userInput = "new input";

接口(Interfaces)

接口声明是为对象类型命名的另一种方式:

interface Point {
  x: number;
  y: number;
}

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

就像我们上面使用类型别名时一样,这个例子与使用匿名对象类型完全相同。TypeScript 关注的是我们传递给 printCoord 的值的结构 —— 它只关心这个值是否具有预期的属性。正因为 TypeScript 仅关注类型的结构和能力,这也是我们称 TypeScript 为结构化类型系统的原因。

类型别名(Type Aliases)与接口(Interfaces)之间的区别

类型别名和接口非常相似,在许多情况下,你可以自由选择使用它们。几乎接口的所有特性都可以在类型别名中使用,主要的区别在于类型别名在创建后不能重新打开以添加新属性,而接口始终是可扩展的。

特性接口(Interface)类型别名(Type)
扩展可以通过 extends 扩展接口可以通过交集(&)扩展类型
添加新字段接口支持声明合并,可以添加新的字段类型别名不能在创建后更改
声明合并支持声明合并不支持声明合并
用途用于声明对象的形状,不能重命名原始类型可用于声明对象的形状,也可以重命名原始类型(例如,string、number)

扩展对比

// 扩展接口
interface Animal {
  name: string;
}

interface Bear extends Animal {
  honey: boolean;
}

const bear = getBear();
bear.name;
bear.honey;
// 通过交集扩展类型
type Animal = {
  name: string;
}

type Bear = Animal & { 
  honey: boolean;
}

const bear = getBear();
bear.name;
bear.honey;

// 声明合并对比

// 向现有接口添加新字段
interface Window {
  title: string;
}

interface Window {
  ts: TypeScriptAPI;
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
// 类型别名不能修改
type Window = {
  title: string;
}

type Window = {
  ts: TypeScriptAPI;
}

// 错误:重复的标识符 'Window'。

你将在后续文章中了解更多关于这些概念的内容,所以如果现在没有完全理解,不用担心。

  • 在 TypeScript 4.2 版本之前,类型别名的名称可能会出现在错误消息中,有时会替代等效的匿名类型(这可能是你想要或不想要的行为)。而接口在错误消息中始终会显示其名称。
  • 类型别名不能参与声明合并,而接口可以。
  • 接口只能用于声明对象的形状,不能用来重命名原始类型。
  • 接口的名称在错误消息中总是以其原始形式出现,但只有在按名称使用时才会显示。
  • 使用接口与 extends 进行扩展通常比使用类型别名与交集(&)更能提高编译器的性能。

在大多数情况下,你可以根据个人偏好选择使用接口或类型别名,TypeScript 会在你需要另一种声明时告诉你。如果你需要一个启发式建议,可以使用接口,直到你需要类型别名的特性。