我有一个cart reducer功能,可以添加、更新和删除案例。我在redux商店里也有一个产品阵列。当有两个项目添加到乘积数组中时,我增加数量值,而不是两个项目。我的主要问题是,减速器是否应该包括任何逻辑,即确定产品阵列是否已经包含确切的产品,并且只返回产品数量的更新,或者是否应该在现有产品的演示组件检查中处理这种行为,并添加新产品或更新数量?
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
}
}
根据Redux常见问题解答条目,在减速器和动作创建者之间划分逻辑:
没有一个明确的答案来确切地回答减速器或动作创建者中应该包含哪些逻辑片段。一些开发人员更喜欢有"胖"的动作创建者,而"瘦"的减少器只是简单地获取动作中的数据,然后盲目地将其合并到相应的状态。其他人则试图强调保持操作尽可能小,并尽量减少在操作创建者中使用getState()。(就这个问题而言,其他异步方法,如sagas和observable,属于"动作创建者"类别。)
在减速器中加入更多的逻辑有一些潜在的好处。动作类型可能更具语义和更有意义(例如"USER_UPDATED"而不是"SET_STATE")。此外,在减速器中有更多的逻辑意味着更多的功能将受到时间旅行调试的影响。
这条评论很好地总结了这种二分法:
现在,问题是在动作创建者中放入什么,在减缩器中放入什么——在胖和瘦动作对象之间进行选择。如果你把所有的逻辑都放在动作创建者中,你最终得到的是胖动作对象,它们基本上声明了状态的更新。Reducer变得纯粹、愚蠢、添加这个、删除那个、更新这些函数。它们将很容易创作。但你的商业逻辑不会太多。如果你在reducer中放入更多的逻辑,你最终会得到漂亮的、精简的操作对象,大部分数据逻辑都在一个地方,但你的reducer更难组合,因为你可能需要来自其他分支的信息。你最终会得到一个或多个大的减缩器,这些减缩器会从州的高层获得额外的参数。
我也写了我自己对";厚和薄";异径管:
在动作创建者中加入更多逻辑与在减速器中加入更多的逻辑之间存在有效的权衡。我最近看到的一个优点是,如果在减速器中有更多的逻辑,这意味着如果进行时间旅行调试,可以重新运行更多的东西(这通常是一件好事)。
我个人倾向于同时把逻辑放在两个地方。我编写的操作创建者需要时间来确定是否应该调度操作,如果应该,内容应该是什么。然而,我也经常编写相应的reducer,查看操作的内容,并执行一些复杂的状态更新作为响应。
更新
截至2020年,我们特别建议在减速器中放入尽可能多的逻辑:
在任何可能的情况下,都会尝试将计算新状态的逻辑尽可能多地放入适当的reducer中,而不是放入准备和调度操作的代码中(就像单击处理程序)。这有助于确保更多的实际应用程序逻辑易于测试,能够更有效地使用时间旅行调试,并有助于避免可能导致突变和错误的常见错误。
在一些有效的情况下,应该首先计算部分或全部新状态(例如生成唯一的ID),但这应该保持在最低限度。
当然!Reducer应该是纯函数,所以逻辑也必须是纯的。这意味着应该没有任何副作用。副作用包括(但不限于):
- 数据库请求/存储
- 文件IO
- REST/异步调用
- 全局或外部突变
- 任何类型的数据突变
因此,还原程序永远不应该改变进入的状态,而是返回一个只对副本进行修改的副本,如果是这样的话。不可变的值(如字符串、数字、未定义的值等)可以按原样返回,如果状态没有被修改,也可以原样返回。然而,如果您需要对任何输入进行任何更改,您将返回一个新副本或一个新值。
关于从reducer调用的逻辑,只要所有代码都满足这些要求,那么您就符合Redux模式。
不幸的是,JavaScript无法确定任何给定的代码何时有副作用(其他语言有),所以你应该知道你在调用什么。
如果你失败了,它会破坏Redux吗?不会。但事情可能并不像你(或Flux/Redux开发人员)所期望的那样运作。
不,它们不应该包含逻辑(我的推理)
编辑:多年后,当您可以以某种方式声明依赖项、操作和返回时,我仍然将操作视为段或线程。我觉得reducer是一个通用词,但通常与路由和子路由有关,通常是消息或动作。在react/redux中,我认为它代表了商店的形状,这只是从一个大的动作对象中提取相关值的一种很好的方式。在非反应/redux中,您可以通过多种方式更新状态,例如使用返回、setters、模块全局变量的突变等
在redux中,我只是觉得reducers比任何东西都更能代表您的存储,并且操作/对象既是消息又是处理程序;有效地提供序列化的函数名和参数,以便用调用它。确定要运行的下一个操作并直接传递其参数被认为是编排,而不是更集中在全局/非消息状态的编排。我认为thunks是声明性链+编排,其中sagas将更集中地编排,尽管您可以通过异步(然后与等待)、指定连续函数(成功或错误)来在编排中让步,这些函数在继续之前验证消息中的transactionId/runNumber/threadId是否与全局状态匹配。我认为这是分布式系统设计中的常见做法。
我还发现,在同步环境中,您需要一个操作来处理异步成功/错误的每个连续情况,并且您应该在主线程/承诺队列循环中捕获错误:
queue = queue
.then(() => {
state = onAction(action, state);
})
.catch();
如果你真的想组织这些东西,那么异步,100%使用const
,否则创建一个函数,并将每个小的格式缺陷(缩进)变成一个命名良好的自提升函数。
魔鬼代言人
Array.prototype.reduce
肯定接受一个称为reducer
的处理程序,并且肯定有逻辑,但我认为reducer只是一个流行的通用词,它指的是路由,在这种情况下指的是路径状态,而不是动作(onAction
)。没有行动,或者更确切地说,行动本身就是减少。
原始帖子
在普通的redux中(没有sagas/thunks),动作收集并发射大型对象,这些对象被还原器分解到适当的位置。你应该真正关注行动,而不是减少
您可以在操作创建者的每一步按程序调度操作,而不是大对象,这似乎是最重要的答案(但使用简单的redux是不可能的)
组件:将现有冗余数据传递到动作
操作:收集数据并发射大物体,适用于尽可能多的减速器
Reducers:为细粒度、非重复、平面存储提取操作对象(与单片、冗余、嵌套文件夹相反)。为了便于维护和明显的数据形状,我建议使用显式
initialState
(状态永远不应该改变形状;如果连接的对象不可用,请使用id
序列化,而不是有时嵌套完整对象)。CCD_ 7可以被认为是串行存储器引用或";指针";其可以具有不同的内容但可以是CCD_ 8自身;CCD_ 9是对象引用。。序列化。
*副作用:后续动作侦听器/调度器,当您不想在发出初始事件之前等待时很有用(sagas是基于订阅的,thunks是声明链)
经验法则是尽可能热切地做每件事
热切的操作(早期返回、快速故障、提升状态等)阐明了特定的依赖关系是否同时存在,并使易于决策
热切的操作符合"关注点分离",也是模块化、声明性、有状态、实用、易读代码的一个简单主题:声明依赖项、操作并返回
其他一切都是排序的问题,应该尽可能热切地进行
减速器是你最不想"做"任何事情的地方。它实际上是一个"返回",应该简单地从动作对象中选择数据
考虑一下您可能想在中间件中做什么。。。如果您将所有的逻辑都放在reducer中,那么中间件中的记录将不会应用业务逻辑。
- dispatch(new UpdatePerson(person))
- 运行中间件并拦截UpdatePerson操作
- reducer-更新人员的年龄值
如果你想把你的记录保存在一个存储中,那么reducer逻辑会运行得太晚,你将无法访问中间件中所需状态下的更改记录。