Redux——为什么状态都在一个地方,甚至不是全局的状态?

Redux -- why is state all in one place, even state that isn't global?

我是 React 的新手,甚至是 Redux 的新手。到目前为止,我已经将两者一起用于一个小型沙箱应用程序,我喜欢它们。

不过,当涉及到更大的应用程序时,我开始怀疑这一点: 为什么 Redux 将整个应用程序状态保存在一个存储中?

如果我有一个包含许多不同部分的应用程序(并且每个部分都有自己的组件),对我来说,将每个部分的状态保留给自己(在每个部分的顶级组件中)是有意义的),只要它们的状态不影响其他组件。

当状态的各个部分与其他部分没有任何关系时,我不确定将所有内容都放在一个地方的状态有什么好处。如果组件 A 不受组件 B 的状态影响,反之亦然,它们的状态不应该保存在它们的组件中而不是根目录中吗?

我不能在根部有全局影响的状态,而在它们自己的组件中有特定于每个组件的状态吗?我担心将所有特定于组件的状态一直向上扔到全局状态对象(尤其是当 React 强调自上而下的流程时)。

user-interface 应用程序的全局状态的主要优点是您可以原子地跟踪整个应用程序的状态变化。

tldr;您始终可以轻松保存和预测应用程序的状态,因为只有一个真实来源。

self-state-managed 组件的问题在于它们创建了不可预测的可能状态组合。如果 XComponent 自行更改,您无法轻易判断您的应用程序将处于什么状态,因为您必须争论 YComponent 和 ZComponent,然后让它们了解彼此的状态,以便它们基于此做出决策并确定整体应用。

这真正归结为上下文。如果我有一个依赖于了解应用程序状态的 3 个独立部分的状态的决定,这些部分在 UI 组成方面没有直接关系,我如何获得上下文来做出该决定并传达结果整个应用程序?如果不能访问完整的状态表示,就没有简单的方法来获得上下文聚合。

Redux(以及 Reagent 中的 ratom 等其他模式)通过全局统一状态解决了这个问题。您的操作只是状态更改的使者,但您的 Store 是上下文持有者。如果没有一个单一的商店,您的组件就像封建军阀一样为他们松散相关的领地的状态争吵不休。 Store 是一个紧密结合的寡头政治 (combineReducers()) 的结果,它用铁腕统治你的应用程序状态并阻止错误 :)

全局状态适用于 UI 并解决了很多问题,即使它是 counter-intuitive 甚至对其他类型的软件来说都是不好的做法。也就是说,人们经常注意到并非所有应用程序状态都需要在 Redux 存储中。此外,您可以清除不再有用/相关的数据。最后,仅与给定组件(及其行为/显示)相关的状态不需要反映在全局存储中(必然)。

Redux 中的 separation-of-concerns 抽象是 reducer,因为您可以创建多个 reducer 并将它们组合起来以创建 logic-chain 用于存储更新。

使用 reducer,你仍然是 "separating" 代码中的状态逻辑,但实际上当 运行 时,它都被视为一棵树。这使您的状态更改可预测且原子化,但允许良好的组织、封装和关注点分离。

为什么我们要将持久状态移动到数据库中,而不是让每个后端组件在单独的文件中管理它们的状态?

因为它使查询、调试和序列化我们的整体应用程序状态变得更加容易。

Redux 的灵感来自一种叫做 Elm 的语言,它也提倡使用单一模型。 Elm 的创建者进一步说明了为什么这是应用程序的重要品质。

There is a single source of truth. Traditional approaches force you to write a decent amount of custom and error prone code to synchronize state between many different stateful components. (The state of this widget needs to be synced with the application state, which needs to be synced with some other widget, etc.) By placing all of your state in one location, you eliminate an entire class of bugs in which two components get into inconsistent states. We also think you will end up writing much less code. That has been our observation in Elm so far.

我发现这个概念更容易学习和理解,同时从项目的 ClojureScript Re-frame and watching David Nolen's videos on Om Next. The Re-frame README 中解决它也是一个很好的学习资源。

以下是我的一些个人观点,说明为什么全局状态是更优雅的解决方案。

更简单的组件

在许多基于有状态组件的应用程序中出现的一个常见场景是需要修改存在于另一个组件中的状态。

例如,单击 NameTag 组件中的编辑按钮应打开一个编辑器,允许用户修改处于 Profile 组件状态( NameTag)。解决这个问题的方法是向下传递处理程序回调,然后将数据传播回组件树。这种模式导致 React 应用程序现有单向数据流中的子数据流混乱。

对于全局状态,组件只需分派触发该状态更新的操作。组件和操作都可以参数化以将上下文信息发送回用户(例如,我应该编辑其姓名的用户的 ID 是什么)。

您不必考虑如何将更改传达给状态,因为您确切地知道状态在哪里,而操作是将这些更改发送到那里的预定义机制。

纯函数

当您的应用程序状态存在于单个位置时,呈现它的组件可以是将状态作为参数的纯函数。他们只查看数据,return 可以调度操作以进行更新的视图。

这些函数是引用透明的,这意味着对于任何给定的输入集,总是有完全相同的输出。这是测试的理想选择,您最终会得到易于测试的组件。

序列化

在具有状态组件的传统 React 应用程序中,序列化整个应用程序状态将是一场噩梦。这将涉及遍历整个组件树并从每个组件中提取状态值,然后将它们全部收集到数据结构中并对其进行编码。

重新填充具有序列化状态的组件意味着类似的过程,除了您还必须弄清楚哪些类型的组件负责哪些数据,以便您可以准确地重新创建组件树。这也意味着存储有关您所在州的组件类型的其他信息。

使用全局状态,您可以简单地对其进行精确编码和解码。

Undo/Redo

要使用全局状态实现 undo/redo,您需要将状态存储在列表中,而不是在更新时替换最后一个。索引指针可以控制你当前所处的状态。

对于有状态组件,这将要求每个组件都实现此机制。这不仅是很多额外的工作,而且还会在您的应用程序中创建另一个要引入错误的点。正如他们所说,最好的代码就是没有代码。

动作回放

在 Redux(以及一般的 Flux 模式)中,我们可以跟踪已经播放的动作,然后在另一个上下文中播放它们以产生完全相同的状态。

如果引入组件本地状态,那么就可以和这个说拜拜了。对本地状态的更新可以来自 DOM 事件处理程序、网络请求、异步操作(以及更多)。这些操作不能序列化,也就是不能回放。