协慌网

登录 贡献 社区

为什么使用 Redux 而不是 Facebook Flux?

我已经阅读了这个答案减少了样板 ,看了几个 GitHub 示例,甚至尝试了 redux(todo apps)。

据我了解, 官方 redux doc 动机提供了与传统 MVC 架构相比的优点。但它没有提供问题的答案:

为什么你应该使用 Redux 而不是 Facebook Flux?

这只是编程风格的问题:功能与非功能?或者问题是在 redux 方法中遵循的能力 / 开发工具?也许缩放?还是测试?

如果我说 redux 对于来自函数式语言的人来说是一种变化,我是对的吗?

要回答这个问题,您可以比较实施 redux 在 flux 和 redux 上的动机点的复杂性。

以下是官方 redux doc 动机的动机点:

  1. 处理乐观的更新( 据我所知,它几乎不取决于第 5 点。难道在 facebook flux 中实现它吗?
  2. 在服务器上渲染( facebook flux 也可以做到这一点。与 redux 相比有什么好处?
  3. 在执行路由转换之前获取数据( 为什么在 facebook 中无法实现?有什么好处?
  4. 热重载( 可以使用React Hot Reload 。为什么我们需要 redux?
  5. 撤消 / 重做功能
  6. 还有其他一点吗?像坚持国家一样......

答案

Redux 的作者在这里!

Redux 与 Flux 没有什么不同。总体而言,它具有相同的架构,但 Redux 能够通过使用 Flux 使用回调注册的功能组合来削减一些复杂角落。

Redux 没有根本的区别,但是我发现它使得某些抽象更容易,或者至少可以实现,这在 Flux 中很难或不可能实现。

减速剂成分

以分页为例。 My Flux + React Router 示例处理分页,但代码很糟糕。可怕的原因之一是Flux 使得跨商店重用功能变得不自然。如果两个商店需要处理分页以响应不同的操作,它们或者需要从公共基本存储继承(糟糕!当您使用继承时将自己锁定到特定设计中),或者从内部调用外部定义的函数事件处理程序,需要以某种方式操作 Flux 存储的私有状态。整件事情很混乱(虽然绝对是可能的)。

另一方面,由于减速器的成分,Redux 的分页很自然。它一直是减速器,因此您可以编写一个减速器工厂来生成分页减速器 ,然后在减速器树中使用它 。这么简单的关键是因为在 Flux 中,商店是平的,但在 Redux 中,Reducer 可以通过功能组合嵌套,就像 React 组件可以嵌套一样。

此模式还支持非用户代码撤消 / 重做等精彩功能。 你能想象将 Undo / Redo 插入一个两行代码的 Flux 应用程序吗?几乎不。使用 Redux,由于减速器组成模式,它是重要的 。我需要强调的是,没有什么新鲜事 - 这是Elm Architecture 中开创并详细描述的模式,它本身受到 Flux 的影响。

服务器渲染

人们使用 Flux 在服务器上渲染得很好,但是看到我们有 20 个 Flux 库,每个都试图让服务器呈现 “更容易”,也许 Flux 在服务器上有一些粗糙的边缘。事实是 Facebook 没有做太多的服务器渲染,所以他们并没有非常关注它,并依靠生态系统来使它更容易。

在传统的 Flux 中,商店是单身人士。这意味着很难为服务器上的不同请求分离数据。不是不可能,但很难。这就是为什么大多数 Flux 库(以及新的Flux Utils )现在建议您使用类而不是单例,因此您可以按请求实例化存储。

您仍需要在 Flux 中解决以下问题(您自己或在您喜欢的 Flux 库(如FlummoxAlt )的帮助下):

  • 如果商店是类,我如何使用每个请求的调度程序创建和销毁它们?我什么时候注册商店?
  • 如何保存商店中的数据,然后在客户端上重新水合?我需要为此实现特殊方法吗?

不可否认,Flux 框架(不是 vanilla Flux)可以解决这些问题,但我发现它们过于复杂。例如, Flummox 要求您在商店中实现serialize()和 deserialize deserialize() 。 Alt 通过提供takeSnapshot()自动序列化 JSON 树中的状态来解决这个问题。

Redux 更进一步: 因为只有一个商店(由许多减速器管理),你不需要任何特殊的 API 来管理(重新)水合作用。您不需要 “刷新” 或 “保湿” 商店 - 只有一个商店,您可以读取其当前状态,或创建具有新状态的新商店。每个请求都有一个单独的商店实例。 阅读有关 Redux 服务器渲染的更多信息。

同样,这是 Flux 和 Redux 中可能存在的一种情况,但是 Flux 库通过引入大量 API 和约定来解决这个问题,而 Redux 甚至不必解决它,因为它没有解决这个问题。第一名归功于概念简洁。

开发经验

我实际上并没有打算让 Redux 成为一个受欢迎的 Flux 库 - 我写的是因为我正在研究ReactEurope 关于时间旅行热重载的讨论 。我有一个主要目标: 可以动态更改减速器代码,甚至可以通过交叉操作 “改变过去”,并查看重新计算的状态。

我还没有看到一个能够做到这一点的 Flux 库。 React Hot Loader 也不允许你这样做 - 事实上,如果你编辑 Flux 商店它会中断,因为它不知道如何处理它们。

当 Redux 需要重新加载 reducer 代码时,它会调用replaceReducer() ,并且应用程序将使用新代码运行。在 Flux 中,数据和功能纠缠在 Flux 存储中,因此您不能 “只更换功能”。此外,您必须以某种方式使用 Dispatcher 重新注册新版本 - Redux 甚至没有。

生态系统

Redux 拥有丰富且快速发展的生态系统 。这是因为它提供了一些扩展点,如中间件 。它的设计考虑了日志 ,支持PromisesObservables路由immutability dev 检查持久性等用例。并非所有这些都会变得有用,但是访问一组可以轻松组合在一起工作的工具是很好的。

简单

Redux 保留了 Flux 的所有优点(记录和重放动作,单向数据流,依赖变异)并增加了新的好处(简单的撤销重做,热重新加载),而不引入 Dispatcher 和商店注册。

保持简单很重要,因为它可以在您实现更高级别的抽象时保持理智。

与大多数 Flux 库不同,Redux API 表面很小。如果删除开发人员警告,注释和完整性检查,则为99 行 。调试没有棘手的异步代码。

您实际上可以阅读它并了解 Redux 的所有内容。


另请参阅我对使用 Redux 与 Flux 相比的缺点的答案

在 Quora,有人说

首先,完全可以使用 React 编写应用程序而不使用 Flux。

另外,我创建的这个视觉图表显示了两者的快速视图,对于不想阅读整个解释的人来说可能是一个快速的答案: Flux vs Redux

但如果您仍然对更多信息感兴趣,请继续阅读。

我相信你应该从纯粹的 React 开始,然后学习 Redux 和 Flux。在您对 React 有一些实际经验之后,您将看到 Redux 是否对您有所帮助。

也许你会觉得 Redux 完全适合你的应用程序,也许你会发现,Redux 正试图解决你并没有真正遇到的问题。

如果你直接使用 Redux,你最终可能会得到过度设计的代码,代码难以维护,甚至更多的错误,而不是没有 Redux。

来自Redux 文档

动机
随着 JavaScript 单页面应用程序的要求变得越来越复杂,我们的代码必须管理比以往更多的状态。此状态可以包括服务器响应和缓存数据,以及尚未持久保存到服务器的本地创建的数据。 UI 状态的复杂性也在增加,因为我们需要管理活动路径,选定选项卡,微调器,分页控件等。

管理这个不断变化的状态很难。如果模型可以更新另一个模型,则视图可以更新模型,该模型会更新另一个模型,而这反过来可能会导致另一个视图更新。在某些时候,您不再理解您的应用中发生了什么,因为您已经失去了对其状态的时间,原因和方式的控制。当系统不透明且不确定时,很难重现错误或添加新功能。

好像这还不够糟糕,考虑新要求在前端产品开发中变得普遍。作为开发人员,我们期望处理乐观更新,服务器端呈现,在执行路由转换之前获取数据等等。我们发现自己试图管理以前从未处理过的复杂性,我们不可避免地提出这样一个问题:是时候放弃了吗?答案是不。

这种复杂性很难处理,因为我们混合了人类思维难以推理的两个概念:变异和异步性。我称他们为 Mentos 和 Coke。两者在分离时都很棒,但它们一起造成混乱。像 React 这样的库试图通过删除异步和直接 DOM 操作来解决视图层中的这个问题。但是,管理数据状态取决于您。这是 Redux 的用武之地。

跟随 Flux,CQRS 和 Event Sourcing 的脚步,Redux 试图通过对更新发生的方式和时间施加某些限制来使状态突变可预测。这些限制反映在 Redux 的三个原则中。

也来自Redux docs

核心概念
Redux 本身很简单。

想象一下,您的应用程序的状态被描述为一个普通对象。例如,todo 应用程序的状态可能如下所示:

{
  todos: [{
    text: 'Eat food',
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED'
}

这个对象就像一个 “模型”,除了没有 setter。这样代码的不同部分不能任意改变状态,导致难以重现的错误。

要更改状态中的某些内容,您需要发送操作。一个动作是一个简单的 JavaScript 对象(请注意我们如何不引入任何魔法?)来描述发生了什么。以下是一些示例操作:

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

强制将每个更改描述为一个操作,让我们清楚地了解应用程序中发生的情况。如果发生了变化,我们就知道它为什么会改变行动就像发生了什么的面包屑。最后,为了将状态和动作联系在一起,我们编写了一个名为 reducer 的函数。再一次,它没有什么神奇之处 - 它只是一个将状态和动作作为参数的函数,并返回应用程序的下一个状态。为大型应用程序编写这样的函数会很困难,所以我们编写管理状态部分的较小函数:

function visibilityFilter(state = 'SHOW_ALL', action) {
  if (action.type === 'SET_VISIBILITY_FILTER') {
    return action.filter;
  } else {
    return state;
  }
}

function todos(state = [], action) {
  switch (action.type) {
  case 'ADD_TODO':
    return state.concat([{ text: action.text, completed: false }]);
  case 'TOGGLE_TODO':
    return state.map((todo, index) =>
      action.index === index ?
        { text: todo.text, completed: !todo.completed } :
        todo
   )
  default:
    return state;
  }
}

我们编写另一个 reducer 来管理应用程序的完整状态,方法是调用相应状态键的两个 reducers:

function todoApp(state = {}, action) {
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  };
}

这基本上是 Redux 的整个想法。请注意,我们没有使用任何 Redux API。它附带了一些实用程序来促进这种模式,但主要的想法是描述你的状态如何随着时间的推移而更新以响应操作对象,并且你编写的 90%的代码只是简单的 JavaScript,没有使用 Redux 本身,它的 API,或任何魔术。

你可能最好开始阅读 Dan Abramov 的这篇文章,他在撰写 redux 时讨论了 Flux 的各种实现及其权衡: Flux 框架的演变

其次,你链接的动机页面并没有真正讨论 Redux 的动机,而是 Flux(和 React)背后的动机。 三原则更具特定于 Redux,但仍然没有处理与标准 Flux 架构的实现差异。

基本上,Flux 有多个商店,可以响应 UI / API 与组件的交互来计算状态变化,并将这些更改作为组件可以订阅的事件进行广播。在 Redux 中,每个组件只有一个商店订阅。 IMO 感觉至少像 Redux 通过统一(或减少,如 Redux 所说)数据流回到组件来进一步简化和统一数据流 - 而 Flux 专注于统一数据流的另一面 - 查看到模型。