【问题标题】:React Redux: Should reducers contain any logicReact Redux:reducers 是否应该包含任何逻辑
【发布时间】:2026-01-31 12:05:02
【问题描述】:

我有一个带有添加、更新和删除案例的购物车减速器功能。我在 redux 商店中也有一个产品数组。 当产品数组中添加了两个项目时,我增加了数量值,而不是两个项目。我的主要问题是,reducers 是否应该包含任何逻辑,即确定 products 数组是否已经包含确切的产品并且只返回产品数量的更新,或者是否应该在检查现有产品的演示组件中处理此行为并添加新的产品或更新数量?

function CartReducer (state = initialState, action) {
  switch (action.type) {
    case AddToCart:
      return {
        ...state,
        products: [...state.products, action.product],
        totalPrice: state.totalPrice += (action.price * action.quantity)
      }

    case RemoveItemCart:

      return {
        ...state,
        products: [
          ...state.products.slice(0, action.index),
          ...state.products.slice(action.index + 1)
        ]
      }

    case UpdateItemQuantity:
      return {
        ...state,
        products: state.products.map((product, index) => {
          if (index === action.index) {
            return Object.assign({}, product, {
              quantity: action.quantity
            })
          }
          return product
        })
      }

    default:
      return state
  }
}

【问题讨论】:

  • reducer 可以有任何你想要的逻辑,只要它们保持纯净。 pure 意味着没有副作用,例如异步调用或更改或依赖其上下文之外的对象,也没有改变它们的 state
  • React+redux 是前端,CQRS+Event sourcing 是后端。调度动作本质上是将消息放在事件队列中。在它们上应用减速器基本上是处理存储。所以,在我看来,进入 reducer 的逻辑应该只与状态的存储方式有关,包括。优化、一致性和其他东西。
  • 参见youtube/fU9hR3kiOK0?t=21m6s 以供参考 - 您可以将事件流想象为动作。当他谈到物化视图时,redux store 将是主要的。然后,Reducer 将是一个函数,它可以根据操作递增地转换存储。 Reducer 需要是不可变的——所有副作用都需要通过操作来处理。至于其他物化视图,它们可以被认为是选择器应用于状态的中间状态。本质上,组合reducer和selector仍然是一个转换函数。

标签: reactjs react-native redux react-redux


【解决方案1】:

the Redux FAQ entry on splitting logic between reducers and action creators:

对于 reducer 或 action creator 中应该包含哪些逻辑,没有一个明确的答案。一些开发人员更喜欢拥有“胖”的动作创建者,而“瘦”的减速器只是简单地将数据放入动作中并盲目地将其合并到相应的状态中。其他人试图强调保持动作尽可能小,并尽量减少 getState() 在动作创建者中的使用。 (就这个问题而言,其他异步方法,例如 sagas 和 observables 属于“动作创建者”类别。)

在你的 reducer 中加入更多的逻辑有一些潜在的好处。操作类型可能更语义化和更有意义(例如“USER_UPDATED”而不是“SET_STATE”)。此外,reducer 中的逻辑越多,意味着更多的功能会受到时间旅行调试的影响。

这条评论很好地总结了二分法:

现在,问题是在动作创建器中放什么,在减速器中放什么,在胖和瘦动作对象之间进行选择。如果你把所有的逻辑都放在动作创建器中,你最终会得到基本上声明状态更新的胖动作对象。 Reducers 变得纯粹、愚蠢、添加这个、删除那个、更新这些功能。他们将很容易组成。但是你的业务逻辑并不多。如果你在 reducer 中加入更多逻辑,你最终会得到漂亮、精简的 action 对象,大部分数据逻辑都在一个地方,但是你的 reducer 更难组合,因为你可能需要来自其他分支的信息。你最终会得到大型 reducer 或从 state 更高层获取额外参数的 reducer。

我还写了my own thoughts on "thick and thin" reducers

在 action creators 中添加更多逻辑与在 reducer 中添加更多逻辑之间存在有效的权衡。我最近看到的一个好处是,如果你在 reducer 中有更多的逻辑,这意味着如果你在进行时间旅行调试(这通常是一件好事),那么可以重新运行更多的东西。

我个人倾向于同时将逻辑放在这两个地方。我编写动作创建器需要时间来确定是否应该分派一个动作,如果是,内容应该是什么。不过,我也经常编写相应的 reducer,查看 action 的内容并执行一些复杂的状态更新作为响应。

更新

截至 2020 年,we specifically recommend putting as much logic as possible in reducers

在可能的情况下,尽量将计算新状态的逻辑放入适当的 reducer,而不是放在准备和调度操作的代码中(如点击处理程序)。这有助于确保更多实际应用逻辑易于测试,更有效地使用时间旅行调试,并有助于避免可能导致突变和错误的常见错误。

在某些情况下,应首先计算部分或全部新状态(例如生成唯一 ID),但应保持在最低限度。

【讨论】:

    【解决方案2】:

    当然! Reducer 应该是纯函数,所以逻辑也必须是纯函数。这意味着应该有零副作用。副作用包括(但不限于):

    • 数据库请求/存储
    • 文件 IO
    • REST/异步调用
    • 全局或外部突变
    • 任何类型的数据突变

    因此,reducers 永远不应该改变传入的状态,而是返回一个仅对副本进行修改的副本,如果那样的话。不可变值(如字符串、数字、未定义等)可以按原样返回,状态也可以返回,如果它没有被修改的话。但是,如果您需要对任何输入进行任何更改,您将返回一个新副本或一个新值。

    关于从你的 reducer 调用的逻辑,只要所有代码都满足这些要求,那么你就符合 Redux 模式。

    不幸的是,JavaScript 无法确定任何给定代码何时具有副作用(其他语言有),因此您应该知道自己在调用什么。

    如果你失败了,它会破坏 Redux 吗?不会。但事情可能不会完全按照您(或 Flux/Redux 开发人员)的预期工作。

    【讨论】:

      【解决方案3】:

      不,它们不应该包含逻辑

      Actions 收集并发出大型对象,reducers 将它们拆分到适当的位置

      您可以在动作创建器中的每一步按程序调度动作,而不是大型对象,这似乎是最佳答案所提倡的 (yet is impossible with plain redux)


      • 组件:将现有的 redux 数据传递到操作中

      • Actions:收集数据并发出大对象,适用于尽可能多的 reducer

      • Reducers:提取操作对象以进行粒度、非重复、平面存储(相对于单一、冗余、嵌套的文件夹)。我推荐显式的 initialState 以便于维护和明显的数据形状(状态永远不应该改变形状;如果连接的对象不可用,通过id 序列化而不是有时嵌套完整的对象)。

        id 可以看作是序列化的内存引用或“指针”,它可能具有不同的内容,但可以是 === 对自己; id 是对象引用.. 序列化。

      • *副作用: 后续动作侦听器/调度程序,当您不想在发出初始事件之前等待时很有用(sagas 是基于订阅的,thunk 是声明性链)


      经验法则是尽可能热切地做每一件事

      Eager 操作(提前返回、快速失败、提升状态等)明确特定依赖项是否同时存在,并便于决策

      急切操作符合“关注点分离”和模块化、声明性、有状态、实用、易读代码的简单主题:声明依赖关系、操作和返回

      其他一切都是顺序问题,应该尽快完成

      reducer 是你最不想“做”任何事情的地方。它实际上是一个“返回”,应该简单地从操作对象中选择数据

      【讨论】:

        【解决方案4】:

        考虑一下您可能想在中间件中做什么...如果您将所有逻辑都放在 reducer 中,那么中间件中的记录将不会应用业务逻辑。

        1. dispatch(new UpdatePerson(person))
        2. 运行中间件并拦截 UpdatePerson 操作
        3. reducer - 更新人的年龄值

        如果您想说,将记录保存在存储中,那么 reducer 逻辑将运行得太晚,并且您将无法访问中间件中所需状态下更改的记录。

        【讨论】: