【翻】Mastering TypeScript: A Guide to Choosing Between ‘type’ and ‘interface’

为了提高英语水平和保持技术成长,开始按计划翻译一些短篇和博客,有问题欢迎讨论👻
原文:Mastering TypeScript: A Guide to Choosing Between ‘type’ and ‘interface’
原作者:Rico Fritzsche

正文

TypeScript现在已经成为捕捉错误和提高代码稳定性的可靠工具。然而,在使用TypeScript时,我经常想知道type和interface之间的区别,以及如何使用它们

在这篇博客中,我想分享我所知道的的关于type和interface之间的区别,以及何时使用它们,我会提供明确的例子和解释,帮助你更好地理解如何在TypeScript项目中使用这两个强大的工具。

在本篇文章结束时,你将对type和interface之间的区别有更好的理解,以及知道如何在TypeScript代码中更有效地使用它们。我希望这篇文章对希望提高TypeScript技能和希望编写更多可靠代码的web开发者有所帮助。

Type vs Interface

在TypeScript中,type和interface都可以用来定义对象类型,但他们在语法和功能上有一些区别,首先interface是一个用来定义对象类型关键字,其语法是:

interface IUser {
  name: string;
  age: number;
  ...
}

另一方面,type也是一个定义对象类型的关键字,其语法是:

type User = {
  name: string;
  age: number;
  ...
};

语法差异不大,但在功能上还有其他差异。

interface可以扩展并创建继承父interface属性的新类型:

interface IUser {
  name: string;
}

interface IMember extends IUser {
  department: string;
}

这允许你定义一套多个类型共享的基础属性,然后根据需要为更多的特定类型进行扩展。这可以帮助你避免代码重复,使你的代码更具有模块化和组织。

而type不能以同样的方式被扩展,但它可以用来定义联合、交叉和其他复杂类型:

type Union = Type1 | Type2;

type Intersection = Type1 & Type2;

这些type允许你定义类型更复杂的关系,并且可以帮助你更准确地建立数据模型。

Example — Building a Messaging App

假如你正在构建一个消息app,你想为一条信息定义一个类型,它可以包含文本或图像,你可以使用一个联合类型来定义:

type Message = {
  type: 'text' | 'image';
  content: string | File;
}

在这个例子中,我们使用type来定义一个联合类型,它只能是两个字符串值中的一个:”text”或者”image”,我们还使用联合类型来定义content属性,他可以是一个字符串(text messages)或者是一个File对象(image messages)。

现在,假如你想在你的app中为一组拥有不同角色的用户定义一个类型,比如管理员、版主和普通用户。你可以使用一个交叉类型来定义:

type User = {
  id: number;
  name: string;
}

type Admin = User & {
  role: 'admin';
  permissions: string[];
}

type Moderator = User & {
  role: 'moderator';
  canDelete: boolean;
}

type RegularUser = User & {
  role: 'user';
  isPremium: boolean;
}

在这个例子中,我们定义了一个有两个属性的用户类型:id和name。然后我们定义了另外三个类型(Admin、Moderator和RegularUser),它们使用一个交叉类型(&)来扩展User类型。这些类型中每一个都有一个不同的role属性和针对其角色的额外属性。

使用联合类型和交叉类型可以帮助你在TypeScript代码中定义更复杂的类型,并使其更容易对现实世界的数据进行建模。

Declaration Merging

interface还支持声明合并,它允许你定义多个同名的interface,并将它们的属性合并到一个interface中:

interface MyObject {
  property1: Type1;
}

interface MyObject {
  property2: Type2;
}

const myObject: MyObject = {
  property1: 'value1',
  property2: 'value2'
};

当你与第三方库或系统一起工作时,这很有用,它们分别定义了它们的interface,然后在需要时把它们合并成一个interface。

Example — Unleashing the Power of Declaration Merging

假如你正在构建一个需要用户登录的app,你想为认证API返回的用户对象定义一个interface。你可以像这样定义这个interface:

interface User {
  username: string;
  email: string;
}

后来,你发现认证API也会为每个用户返回一个userId属性,但你不想修改原来的User interface。相反,你可以定义一个同名的新interface,它将自动将属性与原来的User interface合并:

interface User {
  userId: number;
}

// Now the User interface has three properties: username, email, and userId
const user: User = {
  username: 'john.doe',
  email: 'john.doe@example.com',
  userId: 123
};

在这个例子中,我们定义了User interface, 有两个属性:username和email,然后,我们定义了一个新的User interface,并带有userId属性。当我们使用User interface来定义我们的用户对象时,两个接口的属性会被合并在一起,创建一个具有三个属性的对象:username,email和userId。

声明合并是interface一个很有用的功能,它允许你在不修改原有定义的情况下扩展现有interface。

Let’s talk about the Open/Closed Principle

开闭原则(OCP),是面向对象编程设计SOLID原则之一。它就像软件开发的圣杯。每个人都在谈论它,但是你们真的了解它吗?还只是假装了解?

我希望你记住:SOLID是一个缩写,代表了Robert C.Martin(也被称为Bob叔叔)提出的面向对象编程设计的五个原则。这些原则使软件开发具有可伸缩、可维护、可扩展

之前的例子很好的说明了开闭原则,因为它展示了如何在不修改原有interface的情况下扩展它。通过定义一个带有userId属性的新interface,而不是修改原来的User interface,在保持原来的接口不被修改的同时还能扩展其功能。这种方式随着时间的推移,使你的代码更容易维护和扩展,而不会引入意想不到的问题和破坏式变化。

但是等等,还有更多!如果你使用TypeScript或其他具有声明合并功能的语言,你可以只用两个属性来定义user interface,然后用userId属性定义一个新的user interface,但你用User interface定义用户对象时,两个接口的属性会神奇地合并在一起,创建一个有三个属性的对象。这就像魔术一样,只不过没有兔子,更多的是TypeScript。

但是实际中,谁有时间去研究OCP这些废话?有时你只是需要完成一些功能,而修改现有代码是最快的办法。谁会关心OCP呢?它只是软件开发者喜欢抛出的一个花哨的原则,让自己听起来很聪明。

所以,如果你必须做的话,就去违反OCP吧。只要确保你有一个很好的理由这样做。如果有人问起,就告诉他们你是在遵循KCP(保持满足原则)。这是一个我们都支持的原则。

React Component Props

所以你是想用type还是interface来定义React component props?让我告诉你,你来对地方了,我的意思是,当你可以将时间花在探讨TypeScript语法的细微差别时,谁还需要生活呢?你就像现代的苏格拉底,提着重要的问题,比如“我应该是用type还是interface?”而我们这些凡人则忙着在Tinder上左滑右滑。所以,请系好安全带,我的朋友,因为我们即将深入探索React Components 类型定义的精彩世界

在React组件上使用type还是interface来定义props在很大程度上是一个个人偏好的问题,因为这两个选项提供了相似的功能。然而,在选择它们的时候,有一些区别需要考虑。

首先interface是React组件中定义props的一种常用方式。因为interface允许你扩展和重用来定义props对象。例如,你可以创建一个基本props interface,然后在每个单独的组件中扩展它

interface Props {
  className?: string;
  onClick?: () => void;
}

interface ButtonProps extends Props {
  type?: 'button' | 'submit' | 'reset';
}

function Button({ className, onClick, type }: ButtonProps) {
  // render button with props
}

在这个例子中,我们定义了一个包含className和onClick属性的interface。然后我们扩展这个interface,创建了一个ButtonProps interface,增加了一个可选的type prop。然后用这个interface来定义Button组件的props。

另一方面,当你需要定义更复杂的类型时,比如联合或者交叉类型时,type就很有用。比如,你可以使用type来定义一个size options,代表一个组件的不同size选项。

type Size = 'small' | 'medium' | 'large';

interface Props {
  size: Size;
}

function Component({ size }: Props) {
  // render component with props
}

在这个例子中,我们使用type定义了一个size类型,代表组件的不同尺寸。然后,我们用props interface来定义size prop,要求是必须的并且是尺寸类型中的一个。

通常,使用interface是定义简单props的好选择,可以扩展或重复使用,而使用type是定义复杂类型的好选择。

总之,type和interface都可以用来React组件中定义props,但它们在功能上有一些区别。interface可以扩展或重复使用,而type对于定义更复杂的类型很有用。最终,在type和interface之间选择是一个个人偏好和你项目具体需求的问题。

Summary

一般来说,interface经常被用来定义对象的结构,并作为其他系统或库的interface。另一方面,type经常用来定义更复杂的类型,如联合、交叉或映射类型,或者定义不能用interface表达的类型,如函数类型或条件类型。