Skip to content

第 8 章:决策树 —— 一张图教你选 type 还是 interface

经过前面几章的学习,我们已经了解了 interfacetype 的本质区别、适用场景以及常见反模式。但是当我们真正面临选择时,如何快速做出正确的决策呢?

在这一章中,我们将提供一个清晰的决策树,帮助你在面对具体选择时快速做出判断。

决策树详解

                            ┌─────────────┐
                            │ 你在定义一个类的契约吗? │
                            └─────────────┘

                     ┌───────────────┴───────────────┐
                     是                              否
                     │                               │
         ┌───────────┴───────────┐     ┌─────────────┴─────────────┐
         │ 要被多个类实现吗?         │     │ 是数据组合或配置吗?           │
         └───────────────────────┘     └──────────────────────────┘
                   │                                   │
           ┌───────┴───────┐                   ┌───────┴───────┐
           是              否                  是              否
           │               │                   │               │
    ┌──────▼──────┐ ┌──────▼──────┐    ┌───────▼────────┐ ┌────▼────────┐
    │ 用 interface │ │ 用 type     │    │ 用 type         │ │ 用 type     │
    └─────────────┘ └─────────────┘    └────────────────┘ └─────────────┘

让我们详细解释这个决策树的每一步:

第一步:你在定义一个类的契约吗?

这个问题帮助我们区分面向对象编程和函数式编程的场景。

选择"是"的情况:

  • 你要定义一个类必须实现的方法和属性
  • 你需要使用 implements 关键字
  • 你在设计面向对象的架构

选择"否"的情况:

  • 你只是在给一个复杂类型起别名
  • 你在定义数据结构(如 API 响应、配置对象等)
  • 你在使用函数式编程范式

第二步(左分支):要被多个类实现吗?

如果你确定在定义类的契约,接下来要考虑的是这个契约是否会被多个类实现。

选择"是"的情况:

  • 你定义的是通用接口等
  • 你需要多态性,即同一接口的不同实现
  • 你在设计插件系统或框架

选择"否"的情况:

  • 只有一个类会实现这个接口
  • 你只是为了组织代码而创建接口

第二步(右分支):是数据组合或配置吗?

如果你不是在定义类的契约,那么你很可能在处理数据。

选择"是"的情况:

  • 定义 React 组件的 Props 或 State
  • 定义 API 请求/响应的数据结构
  • 定义配置对象或选项
  • 使用联合类型或交叉类型
  • 定义函数式编程中的代数数据类型

选择"否"的情况:

  • 这个情况实际上很少见,因为大部分非契约场景都是数据相关的

实际应用示例

让我们通过一些实际例子来应用这个决策树:

示例 1:定义数据库 Repository

问题:你在定义一个类的契约吗?
回答:是的,Repository 是一个类的契约

问题:要被多个类实现吗?
回答:是的,可能有 MySQLRepository、PostgreSQLRepository 等实现

结论:使用 interface
typescript
interface UserRepository {
  findById(id: number): Promise<User>;
  save(user: User): Promise<User>;
}

示例 2:定义 React 组件 Props

问题:你在定义一个类的契约吗?
回答:不是,Props 是数据结构

问题:是数据组合或配置吗?
回答:是的,Props 是组件的配置对象

结论:使用 type
typescript
type ButtonProps = {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
  onClick: () => void;
};

示例 3:定义 API 响应

问题:你在定义一个类的契约吗?
回答:不是,API 响应是数据结构

问题:是数据组合或配置吗?
回答:是的,API 响应是数据结构

结论:使用 type
typescript
type UserResponse = {
  id: number;
  name: string;
  email: string;
  createdAt: string;
};

示例 4:定义日志记录器

问题:你在定义一个类的契约吗?
回答:是的,Logger 是一个类的契约

问题:要被多个类实现吗?
回答:是的,可能有 ConsoleLogger、FileLogger、RemoteLogger 等实现

结论:使用 interface
typescript
interface Logger {
  log(message: string): void;
  error(message: string): void;
}

特殊情况处理

有些情况下,决策树可能不能直接给出答案,这时我们需要考虑以下因素:

团队约定

如果团队已经有明确的约定,应该优先遵循团队规范。例如,如果团队约定所有类型都使用 type,那就应该遵守。

生态系统惯例

某些生态系统有自己的惯例。例如,在 React 生态中,组件 Props 通常使用 type;在 Node.js 服务端开发中,服务契约通常使用 interface

未来扩展性

如果预期类型将来可能需要扩展,可以考虑使用 interface,即使当前只有一个实现。

小结

在本章中,我们提供了一个实用的决策树来帮助选择 interfacetype

  1. 首先判断是否在定义类的契约
  2. 如果是契约,再判断是否需要多实现
  3. 如果不是契约,基本上都应该使用 type

这个决策树并不是绝对的规则,而是一个指导原则。在实际应用中,还需要考虑团队约定、生态系统惯例等因素。

下一章,我们将探讨 TypeScript 类型设计的哲学,帮助你从根本上理解如何设计良好的类型系统。


思考题: 你能用这个决策树分析一下你在项目中定义的一个类型应该使用 interface 还是 type 吗?