构建大型可扩展可维护项目的最佳实践
本章主要介绍一种使用 MobX 的不唐突的方式,它在现有的代码库中,或者搭配经典的 MVC 模式表现良好。作为替代方案,还有一种组织 stores 更专用的方式是使用 mobx-state-tree,它具有一些很酷的自带功能: 结构共享快照、动作中间件、JSON 补丁支持,等等。
Stores(存储)
让我们先从 store 开始。在下节中我们还会讨论动作(action)和 React 组件。Store 可以在任何 Flux 系架构中找到,可以与 MVC 模式中的控制器进行比较。Store 的主要职责是将逻辑和状态从组件中移至一个独立的,可测试的单元,这个单元在 JavaScript 前端和后端中都可以使用。
至少两个 store 可以让绝大多数应用从中受益。一个用于 UI 状态,一个或多个用于领域状态。分离这两个 store 的优点是可以重用和测试领域状态,并且可以很好地在其他应用中重用它。然而,UI 状态 store 对于你的应用来说通常非常特别。但通常也很简单。这个 store 通常没有太多的逻辑,但会存储大量的松散耦合的 UI 相关的信息。这是理想状况下的,因为大多数应用在开发过程中会经常性地改变 UI 状态。
通常可以在 UI stores 中找到的:
- Session 信息
- 应用已经加载了的相关信息
- 不会存储到后端的信息
- 全局性影响 UI 的信息
- 可访问性信息
- 当前语言
- 当前活动主题
- 用户界面状态瞬时影响多个、毫不相关的组件:
- 当前选择
- 工具栏可见性, 等等
- 向导的状态
- 全局叠加的状态
对于同构应用程序,你可能还希望使用正常默认值提供这个 store 的存根实现,以便所有组件按预期呈现。可以通过在应用中传递属性到组件树中或使用 包中的 和 来分发 UI 状态 store。
store 示例 (使用 ES6 语法):
领域 store
你的应用应该包含一个或多个领域 store。这些 store 存储你的应用所关心的数据。待办事项、用户、书、电影、订单、凡是你能说出的。你的应用很有可能至少有一个领域 store。
单个领域 store 应该负责应用中的单个概念。然而,单个概念可以采取多个子类型的形式,并且它通常是(循环)树结构。举例来说,一个领域 store 负责产品,一个负责订单。根据经验来说,如果两个概念之间的关系的本质是包含的,则它们通常应在同一个 store 中。所以说一个 store 只是管理 领域对象。
- 实例化领域对象, 确保领域对象知道它们所属的 store。
- 确保每个领域对象只有一个实例。同一个用户、订单或者待办事项不应该在内存中存储两次。这样,可以安全地使用引用,并确保正在查看的实例是最新的,而无需解析引用。当调试时这十分快速、简单、方便。
- 如果从后端接收到更新,则更新现有实例。
- 为你的应用提供一个独立、通用、可测试的组件。
- 要确保 store 是可测试的并且可以在服务端运行,你可能需要将实际的 websocket/http 请求移到单独的对象,以便你可以通过通信层抽象。
- Store 应该只有一个实例。
每个领域对象应使用自己的类(或构造函数)来表示。建议以非规范化形式存储数据。不必把客户端应用的状态看做数据库的一种。真实引用、循环数据结构和实例方法都是 JavaScript 中非常强大的概念。允许领域对象直接引用来自其他 store 的领域对象。记住: 我们想保持我们的操作和视图尽可能简单,并且需要管理引用和自己做垃圾回收可能是一种倒退。不同于其他 Flux 系统架构,使用 MobX 不需要对数据进行标准化,而且这使得构建应用本质上复杂的部分变得更简单:你的业务规则、操作和用户界面。
领域对象可以将其所有逻辑委托给它们所属的 store,如果这更符合你的应用的话。可以将领域对象表示成普通对象,但类比普通对象有一些重要的优势:
- 它们可以有方法。这使得领域概念更容易独立使用,并减少应用所需的上下文感知的数量。只是传递对象。你不需要传递 store,或者必须弄清楚哪些操作可以在对象上应用,如果它们只是作为实例方法可用。
- 对于属性和方法的可见性,它们提供了细粒度的控制。
- 使用构造函数创建的对象可以自由地混合 observable 属性和函数,以及非 observable 属性和方法。
- 它们易于识别,并且可以进行严格的类型检查。
领域 store 示例
组合多个 stores
一个经常被问到的问题就是,如何不使用单例来组合多个 stores 。它们之间如何通信呢?
一种高效的模式是创建一个 来实例化所有 stores ,并共享引用。这种模式的优势是:
- 设置简单
- 很好的支持强类型
当使用 React 时,这个根 store 通常会通过使用 来插入到组件树之中。