在redux中使用样板操作和减速器

psc*_*scl 9 frontend reactjs react-native redux react-redux

通过首先掌握组件props,在组件级别封装UI状态this.state并有选择地通过组件树传递它,我一直在遵循广泛给出的学习React开发的建议.这是一次启发性的经历.我开始欣赏无状态视图设计模式的强大功能,我觉得我已经能够使用这些技术实现健壮且组织良好的结果.

继续,我现在正在尝试使用更复杂的状态管理redux.但是当我redux逐渐深入了解复杂性并集成到我的应用程序中时,我发现自己面临着关于我的代码如何发展的以下观察.其中一些发展似乎是明智的,但其他人让我怀疑我是否做得对'.

1)Action Creators是业务和UI逻辑的关联

我发现之前在React生命周期函数componentDidUpdate等中以及onTouch/onPress处理程序中实现的大部分逻辑现在都在动作创建器中实现.这似乎是一个积极的发展,因为它保持"一切都在同一个地方",并允许单元测试.

问题:将业务逻辑集中在相当复杂的动作创建者网络中是最佳做法吗?

2)挖空了 reducers

作为上面#1的推论,我发现我reducers和他们相应的action对象已经发展成一个事实上的setter列表,它只是用这种方式用传递的值来更新状态存储:

case types.SAVE_ORDER: 
  return Object.assign({}, state, {
    order: action.order,
  });
Run Code Online (Sandbox Code Playgroud)

其中很大一部分原因是它reducers应该是纯函数,因此我对它的功能有限(例如没有异步处理).另外,减速器仅允许在它们各自的商店状态的子部分上操作.鉴于我的应用程序的大部分复杂性已经必然存在于动作创建者中,我发现很难证明任意将复杂性迁移到reducers仅仅是为了使它们"看起来有用".

问题:是否正常,并且可以接受的做法是使样板reducers仅仅作为减少存储状态的美化设置者?

3)redux-thunk无处不在

我已经单独询问了为什么redux-thunk甚至是必要的(而不是在异步回调/效用函数中调用标准动作创建者).我一直指出Dan Abramov的这个答案提供了一个非常令人满意的解释(相对于可扩展性,服务器端渲染和其他原因).

接受了必要性后redux-thunk,我发现我的大多数动作创建者需要执行异步操作,需要访问getStatedispatch对状态进行多次更改.结果我一直在' thunks' 回归' .

问题: redux应用程序广泛依赖于thunk"ed动作创建器"并且很少直接触发标准对象动作是否正常?

4)Redux全球化 this.state

在最后的分析中,似乎我的应用redux商店已经发展到有效地类似于全球this.state.您可以将其视为将整个应用程序状态保留this.state在最外层容器组件中,但没有不可避免的混乱,将所述state向下传递通过嵌套层props,并且任何更改都会通过处理程序的老鼠窝备份组件树功能.

问题:redux是否是用于全球国营商店的正确工具?是否存在类似于react内置的替代方案this.state,允许通过无状态反应组件传播全局应用程序状态,并通过集中的"交换机"从整个应用程序更新,而没有看似无穷无尽的样板文件switch采用redux的常量和语句?

5)单一动作类型? 此后续问题的灵感来自其中一条发布的评论.

问题:合法地(严肃地说,不仅仅是公然展示一个观点)合法地使用具有一种动作类型的redux吗?

示例 - 动作创建者:

export function someActionCreator(various_params){
  return (dispatch, getState => {
    // ... business logic here ....
    asyncIfThisIfThat().then(val => {
      dispatch({
        // type: 'UPDATE_STATE', // Don't even bother setting a type 
        order: val
      })
    })
  )
}
Run Code Online (Sandbox Code Playgroud)

一个通用减速机箱:

export default function app(state = initialState, action = {}) {
  return Object.assign({}, state, action)
  // Just unconditionally merge into state!
}
Run Code Online (Sandbox Code Playgroud)

在我看来,这将提供一个全局范围的状态对象,它自动映射到connected组件,并且受益于不可变状态的所有优点并可与React互操作props.在这个计划中,dispatch有效地成为全球性的setState.


注意 - 请不要误解这个问题 - 这肯定不是对redux的批评.作为一个学习者,我显然无法判断由数千人的专业知识和数百万人的支持所支持的技术.我毫不怀疑它在正确的背景下的价值.

我只是在我自己的代码中感受到一种可疑模式的气味,并想知道如果我做错了什么,或者我是否正在使用正确的工具来完成任务.

Cor*_*son 6

我的答案主要是根据我自己的经验来学习redux并专业地使用它.我所在的团队沿着类似于二传手的行动走下去,然后转移到更基于事件的动作名称,并描述发生的事情,而不是应该发生的事情.

问题:将业务逻辑集中在相当复杂的动作创建者网络中是最佳做法吗?

这取决于您的操作的命名方式.在您的情况下,您的行为是非常美化的制定者,因此您的所有业务逻辑都将存在于Action Creators中.如果您将您的操作命名为更像事件(描述发生什么)而不是设置者,那么您将会将某些业务逻辑转移到reducer中,并且从操作创建者中删除复杂性,因为事件操作自然会感觉更多可在不同的减速器之间重复使用.当您执行setter操作时,趋势是让setter-actions只与1个reducer交互,并在您希望其他reducers参与时创建更多setter-actions.

如果你有一个学校的应用程序,并且学生被驱逐,你可能会派遣一个REMOVE_STUDENT,然后一个DELETE_GRADES_FOR_STUDENT行动.如果您的行为具有类似事件的名称,您可能更倾向于STUDENT_EXPELLED采取减少等级和学生名单减速器都采取行动的行动.

从技术上讲,没有什么可以阻止你拥有类似二传的名字,并且在多个减速器中对它们采取行动.这并不是我的团队在Redux工作并使用类似setter的名字时陷入的倾向.我们不想让简明的行动名称产生的期望和纯度变得混乱,因为这些名称对国家的影响非常明显.REMOVE_STUDENT_GRADESDELETE_STUDENT_FROM_ROSTER感觉很好.

问题:使用样板减速器只是作为减少存储器状态的美化装置,这是正常的,可接受的做法吗?

这是正常的,但不一定正确.这是我们的基本代码最初是如何成长-我们甚至有标准来命名我们作为的行为RESET_...,SET_...,REMOVE_...,ADD_...,UPDATE...等这似乎工作了一段时间,直到我们碰到了,我们需要多个减速按单操作以更新的情况.

您的行为将以这两种方式之一(或两种方式)发展

  1. 连续调度多个操作(如果要连续调度多个操作,则必须使用诸如redux-batch-actions之类的库).我们选择不使用它,因为它很麻烦,并且随着我们的代码库规模不断扩大而感觉它不能很好地缩放.

  2. 将您的操作重命名为更通用,并可在不同的reducer中重复使用.这就是我们最终做的事情.作为制定者和吸气者采取行动是麻烦的.丹·阿布拉莫夫和其他人已经表达了他们的观点,即Redux Actions应该是events(对已经发生的事情的描述),而不是instructions(对应该发生的事情的描述).我工作的团队同意这一点,我们已经摆脱了制定者式的行动.当Redux成为新人时,关于这一点的争论很多.

在方案1中,您可能会执行以下操作:

// student has been expelled from school, remove all of their data
store.dispatch(batchActions(
    removeStudentFromClass(student),
    removeStudentsGrades(student)
));

// student roster reducer
case REMOVE_STUDENT_FROM_CALLS:
    /* ... */

// student grades reducer
case REMOVE_STUDENT_GRADES:
    /* ... */
Run Code Online (Sandbox Code Playgroud)

如果你在不使用Batch Actions的情况下走这条路,那绝对是一场噩梦.每个调度的事件将重新计算状态,并重新呈现您的应用程序.这很快就崩溃了.

// student has been expelled from school, remove all of their data
store.dispatch(removeStudentFromClass(student));
// app re-rendered, students grades exist, but no student!
store.dispatch(removeStudentsGrades(student));
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,您将调度一个操作以从课程中删除学生,然后重新呈现该应用程序.如果您打开了成绩页面,并且您可以看到学生成绩,但学生被删除,您很可能会在学生名册减少器中引用状态以获取学生信息并且可能/将会抛出JS错误.坏消息.你有一个学生的成绩undefined?!我自己遇到了这样的问题,这是我们转向下面的选项2的动机的一部分.你会听到这些被称为"中间状态"的状态并且它们存在问题.

在方案2中,您的代码可能看起来更像这样:

store.dispatch(expelStudent(student));

// student roster reducer
case EXPEL_STUDENT:
    /* ... */

// student grades reducer
case EXPEL_STUDENT:
    /* ... */
Run Code Online (Sandbox Code Playgroud)

使用上面的代码,学生将通过操作被驱逐,并且他们的数据将在一步中从所有Reducer中删除.这可以很好地扩展,您的代码库反映了与您日常谈论的应用相关的业务术语.如果从业务逻辑角度来看,您还可以为多个事件执行相同的状态更新:

case EXPEL_STUDENT:
case STUDENT_DROPPED_OUT:
case STUDENT_TRANSFERRED:
    /* remove student info, all actions must have action.payload.student */
Run Code Online (Sandbox Code Playgroud)

问题:redux应用程序广泛依赖于thunk'ed动作创建者,并且很少直接触发标准对象动作,这是正常的吗?

当然是.一旦你需要从动作创建者的商店中获取一小部分数据,就必须成为一个thunk.Thunk很常见,应该是redux库的一部分.

随着我们的复杂性增加,他们变得混乱,难以理解.我们开始滥用承诺和回报价值,这很费劲.测试它们也是一场噩梦.你必须嘲笑一切,这很痛苦.

为了解决这个问题,我们采用了redux-saga.Redux-saga可以通过诸如redux-saga-test-planredux-saga-test-engine之类的库轻松测试(我们使用测试引擎并根据需要做出贡献).

我们不是100%的传奇,也不是为了.我们仍然根据需要使用thunk.如果您需要将操作升级为更智能,并且代码非常简单,则没有理由不将该操作升级为thunk.

一旦动作创建者变得足够复杂以保证一些单元测试,redux-saga可能会派上用场.

Redux-saga确实有一个粗略的学习曲线,起初感觉很奇怪.手动测试传奇是痛苦的.很棒的学习经历,但我不会再这样做了.

问题:redux是否是用于全球国营商店的正确工具?是否存在类似于react的内置this.state的替代方案,允许全局应用程序状态通过无状态反应组件传播,并通过集中的"交换机"从整个应用程序更新,而没有看似无穷无尽的Web采用redux的样板,常量和开关语句?

MobX - 我从那些对Redux抱怨相同的人那里听到了很多好事(太多的样板文件,太多的文件,一切都断了)我不自己使用它,但没有使用它.你很有可能比Redux更喜欢它.它解决了同样的问题,所以如果你真的喜欢它,那么它可能值得转换.如果您要长时间处理代码,开发人员的经验非常重要.

我对Redux样板和诸如此类的东西没问题.我工作的团队已经制作了宏来支持创建新操作的样板,并且我们已经进行了大量测试,因此我们的Redux代码非常可靠.一旦你使用它一段时间,你内化了样板,它并没有用尽.

如果你坚持使用Redux长期,并且足够精明,可以在redux之上采用流量,那么对于长期可维护性来说,这是一个巨大的胜利.完全类型的redux代码非常棒,尤其适用于重构.重构reducer/actionCreators很容易,但忘记更新单元测试代码.如果您的单元测试被流程覆盖,那么它会抱怨您立即错误地调用了一个函数.太棒了.

介绍Flow是一个很难克服的障碍,但非常值得.我没有参与初始设置,我认为它更容易引入代码库,但我想它需要一些学习和数小时.值得一提.绝对100%值得.

问题:合法地(严肃地说,不仅仅是公然地证明一点)合法地使用redux和一个减速器吗?

你绝对可以,它可以适用于一个小应用程序.对于一个更大的团队来说,它不会很好地扩展,重构似乎会成为一场噩梦.将商店拆分为单独的减速器可以让您分离责任和顾虑.


mar*_*son 5

I\'m a Redux maintainer. I\'ll give you some initial answers and point you to some learning resources, and I can answer further questions as needed.

\n

First, Cory Danielson has given some excellent advice, and I want to echo pretty much everything he said.

\n

Action creators, reducers, and business logic:

\n

I\'ll quote the Redux FAQ entry on splitting business logic between action creators and reducers:

\n
\n

There\'s no single clear answer to exactly what pieces of logic should go in a reducer or an action creator. Some developers prefer to have \xe2\x80\x9cfat\xe2\x80\x9d action creators, with \xe2\x80\x9cthin\xe2\x80\x9d reducers that simply take the data in an action and blindly merge it into the corresponding state. Others try to emphasize keeping actions as small as possible, and minimize the usage of getState() in an action creator. (For purposes of this question, other async approaches such as sagas and observables fall in the "action creator" category.)

\n

There are some potential benefits from putting more logic into your reducers. It\'s likely that the action types would be more semantic and more meaningful (such as "USER_UPDATED" instead of "SET_STATE"). In addition, having more logic in reducers means that more functionality will be affected by time travel debugging.

\n

This comment sums up the dichotomy nicely:

\n
\n

Now, the problem is what to put in the action creator and what in the reducer, the choice between fat and thin action objects. If you put all the logic in the action creator, you end up with fat action objects that basically declare the updates to the state. Reducers become pure, dumb, add-this, remove that, update these functions. They will be easy to compose. But not much of your business logic will be there. If you put more logic in the reducer, you end up with nice, thin action objects, most of your data logic in one place, but your reducers are harder to compose since you might need info from other branches. You end up with large reducers or reducers that take additional arguments from higher up in the state.

\n
\n
\n

I talked about this topic some more in my post The Tao of Redux, Part 2 - Practice and Philosophy earlier this year (specifically the sections on action semantics and thick vs thin reducers, and again in a recent Reddit comment thread.

\n

Use of thunks:

\n

Yes, thunks are a valuable tool for any complex synchronous logic that needs to live outside a component, including any code that needs access to the current store state. They\'re also useful for simple async logic (like basic AJAX calls with just success/failure handlers). I discussed the pros and cons of thunks in my post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability.

\n

In my own app, I use thunks in many places, as well as sagas for more complex async logic and workflows, and highly recommend thunks as a useful tool overall.

\n

Redux as a global store

\n

我经常说,您可以根据需要在 Redux 之上使用尽可能多或尽可能少的抽象。您不必使用 switch 语句,您可以在您的化简器中使用查找表或任何其他条件逻辑,并且强烈鼓励您重用化简器逻辑。事实上,有数十个现有实用程序可以生成可重用的操作创建器和减速器,以及许多在 Redux 之上编写的更高级别的抽象库。

\n

使用单个百叶窗调节器减速器

\n

这是我在 Redux 之道,第 2 部分中看到的另一个主题其他人在最近的另一个 Reddit 帖子中发表了很好的评论。这样做在技术上当然是可行的,但根据 Reddit 评论,你的减速器实际上不再“拥有”状态形状。相反,动作创建者会这样做,并且没有什么可以阻止他们输入对于给定动作类型或减速器没有意义的数据。

\n

正如我在Redux 之道,第 1 部分 - 实现和意图中谈到的,创建 Redux 背后的关键意图之一是您应该能够查看分派操作的日志并了解在哪里/何时/为什么/如何您的状态已更新。虽然 Redux 本身并不关心该action.type字段实际包含什么,但如果分派的操作被有意义地命名,则操作历史记录日志将对您(或其他开发人员)更有意义。"SET_STATE"连续查看 10 个操作并不能告诉您有关正在发生的情况的任何有用信息,虽然您可以查看每个操作的内容以及生成的差异,但"EXPEL_STUDENT"仅通过阅读类似的操作类型就意味着更多。此外,还可以跟踪独特的操作类型在代码库中特定位置的使用位置,从而帮助您隔离数据的来源。

\n

希望这有助于回答您的一些问题。如果您想进一步讨论,我通常会在Discord 上的 Reactiflux 聊天频道中闲逛在美国时间 Discord 晚上很高兴您能过来聊天!

\n