在Redux中使用样板动作和减速器 [英] Use of boilerplate actions and reducers in redux

查看:80
本文介绍了在Redux中使用样板动作和减速器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have been following the widely given advice of learning React development by first mastering component props, encapsulating UI state in component level this.state and passing it down selectively through the component tree. It's been an enlightening experience. I've come to appreciate the power of the stateless view design pattern and I feel that I've been able to achieve robust and well organized results using these techniques.

继续,我现在正尝试使用redux合并更复杂的状态管理.但是,当我审视复杂性并将redux集成到我的应用程序中时,我发现自己面对以下有关我的代码如何演变的观察.这些发展中的一些似乎是明智的,但另一些使我怀疑我是否在做正确的"事情.

Moving on, I am now trying to incorporate more sophisticated state management using redux. But as I wade through the complexity and integrate redux into my apps, I find myself confronting the following observations about how my code has evolved. Some of these developments seem sensible, but others make me question whether I'm doing things 'right'.

1)动作创建者是业务和UI逻辑的纽带

我发现以前在React生命周期函数componentDidUpdate等中以及在onTouch/onPress处理程序中实现的许多逻辑现在已在动作创建者中实现.这似乎是一个积极的进展,因为它可以使一切都在同一个地方"并可以进行单元测试.

I find that much of the logic that was previously implemented in the React lifecycle functions componentDidUpdate etc., and in onTouch/onPress handlers, is now implemented in action creators. This seems to be a positive development as it keeps 'everything in the same place' and allows for unit testing.

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

Question: Is it best practice to concentrate business logic in a web of fairly intricate action creators?

2)空出了reducers

2) Hollowed out reducers

作为上述#1的推论,我发现我的reducers及其对应的action对象已经发展成为事实上的二传手列表,除了用传递的值更新状态存储外,其他工作不多,以这种方式:

As a corollary to #1 above, I find that my reducers and their corresponding action objects have evolved into a de-facto list of setters that do little more than update the state store with the passed along values, in this fashion:

case types.SAVE_ORDER: 
  return Object.assign({}, state, {
    order: action.order,
  });

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

A big part of the reason for this is that reducers are supposed to be pure functions and therefore I'm limited in what I can do with them (e.g. no async processing). Additionally, reducers are allowed only to operate on their respective sub-section of the store state. Given that much of my app's complexity already necessarily resides in the action creators, I find it hard to justify arbitrarily migrating complexity into reducers simply for the sake of making them 'look useful'.

问题:让样板reducers仅仅充当redux存储状态的光荣二传手,这是正常的,可以接受的做法吗?

Question: Is it normal, and acceptable practice to have boilerplate reducers that function merely as glorified setters to the redux store state?

3)redux-thunk无处不在

3) redux-thunk everywhere

我已经分别询问了为什么甚至需要redux-thunk(与在异步回调/实用函数内部调用标准动作创建者相反).我已经指出了Dan Abramov的答案,它提供了非常令人满意的解释(相对于可扩展性,服务器侧面渲染和myraid其他原因).

I've asked separately on SO why redux-thunk is even necessary (as opposed to calling standard action creators inside of async callbacks/utility functions). I've been pointed to this answer by Dan Abramov which provides a very satisfactory explanation (vis-a-vis scalability, server side rendering and myraid other reasons).

已经接受了redux-thunk的必要性,我发现我的大多数<动作>创建者需要执行异步动作,需要访问getStatedispatch对该状态进行多次更改.结果,我一直在广泛地返回'thunks'.

Having accepted the necessity of redux-thunk, I find that the majority of my action creators need to perform async actions, need access to getState, or dispatch multiple changes to the state. As a result I've been returning 'thunks' extensively.

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

Question: Is it normal for a redux application to rely extensively on thunk'ed action creators, and rarely to fire a standard object action directly?

4)还原为全局this.state

4) Redux as global this.state

归根结底,看来我的应用程序的redux存储已发展为有效地类似于全局this.state.您可以将其视为将整个应用程序状态保留在最外层容器组件的this.state中,但不会因将所述state向下传递通过嵌套的props层而引起的不可避免的混乱,并且所有更改都将返回到组件树通过 handler 函数的大鼠嵌套.

In the final analysis, it seems my app's redux store has evolved to effectively resemble a global this.state. You could think of it as keeping the entire application state in this.state in the outermost container component, but without the inevitable mess that comes with passing the said state down through nested layers of props, and any changes back up the component tree through a rats-nest of handler functions.

问题: redux 是用于全局状态存储的正确工具吗?是否存在替代方案,其行为更类似于 react 的内置this.state,允许通过无状态的React组件传播全局应用程序状态,并通过集中化的' 配电盘",而没有看似无休止的样板网,采用 redux 的常数和switch语句?

Question: Is redux the correct tool to use for a global state store? Are there alternatives out there that behave more akin to react's built-in this.state, allowing a global application state to be propagated through stateless react components, and updated from throughout the application via a centralized 'switchboard', without the seemingly endless web of boilerplate, constants and switch statements that come with adopting redux?

5)一种动作类型? 此后续问题的灵感来自于已发布的评论之一.

5) One single action type? This follow up question is inspired by one of the posted comments.

问题:一个人可以合法地(严格地说,不仅仅是公然地证明一个观点)使用具有一种动作类型的redux吗?

Question: Could one legitimately (in all seriousness, not just blatantly demonstrating a point) use redux with precisely one action type?

示例-动作创建者:

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
      })
    })
  )
}

一个通用减速器案例:

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

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

Seems to me this would provide a globally scoped state object that is automatically mapped to connected components, and one that benefits from all the advantages of immutable state and interoperable with React props. In this scheme, dispatch effectively becomes a global setState.

注意-请不要误解这个问题-这肯定不是对 redux 的批评.作为一名学习者,我显然无权评判一种由成千上万的专业知识和数以百万计的支持作为后盾的技术.我毫不怀疑它在适当情况下的价值.

Note - Please don't take this question wrong - this is certainly not criticism of redux. As a learner I am obviously in no position to judge a technology backed by the expertise of thousands and the support of millions. I have no doubt of its value in the right context.

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

I'm just sensing the smell of a questionable pattern in my own code and wondering what, if anything I'm doing wrong, or whether I'm using the right tool for the task.

推荐答案

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

My answer is mostly speaking from my own experience learning redux and using it professionally. The team I was on went down the same path of setter-like actions, and then shifted away to action names that were more event-based and describe what had happened rather than what should happen.

问题:将业务逻辑集中在错综复杂的动作创建者网络中是最佳实践吗?

Question: Is it best practice to concentrate business logic in a web of fairly intricate action creators?

这取决于您的动作的命名方式.就您而言,您的行动是非常光荣的二传手,因此您的所有业务逻辑都将存在于行动创造者"内部.如果您将动作命名为更具事件性(描述发生了什么)而不是设置器,那么您将需要将一些业务逻辑转移到reducer中,并且将复杂性从动作创建者中移除,因为事件动作自然可以在不同的reducer上重用.当您执行setter动作时,趋势是使setter动作仅与1个reducer交互,并在您希望其他reducer参与时创建更多setter动作.

This depends on how your actions are named. In your case, your actions are very glorified setters, so all of your business logic is going to live inside of Action Creators. If you name your actions to be more event-like (descriptive about what happened) rather than setters, you're going to have some of the business logic shift into the reducers, and complexity removed from action creators, because event actions naturally feel more re-usable across different reducers. When you do setter actions, the tendency is to have setter-actions that interact with only 1 reducer, and create more setter-actions when you want other reducers to be involved.

如果您有一所学校的应用程序,并且被开除了学生,则您可能会先发一个REMOVE_STUDENT然后是一个DELETE_GRADES_FOR_STUDENT动作.如果您的动作具有类似事件的名称,则您可能更倾向于使用STUDENT_EXPELLED动作,让成绩降低者和学生名册降低者都对其起作用.

If you have an app for a school, and a student is expelled, you'll likely dispatch a REMOVE_STUDENT and then aDELETE_GRADES_FOR_STUDENT action. If your action has an event-like name, you may be more inclined to just have a STUDENT_EXPELLED action that the grades reducer and student roster reducer both act upon it.

但是,从技术上讲,没有什么可以阻止您使用类似setter的名称,并在多个reducer中对其进行操作.在Redux中工作并使用类似setter的名称时,这并不是我的团队陷入的趋势.我们不想混淆具有简明动作名称的期望和纯正,动作名称对状态的影响非常明显. REMOVE_STUDENT_GRADESDELETE_STUDENT_FROM_ROSTER感觉很好.

There is nothing technically stopping you from having setter-like names, and acting on them in multiple reducers, though. It's just not the tendency that my team fell into when working in Redux and using setter-like names. We didn't want to muddy up the expectations and purity that came from having concise action names, where the impact on state was very clear. REMOVE_STUDENT_GRADES and DELETE_STUDENT_FROM_ROSTER felt good.

问题:让样板减速机仅充当redux存储状态的光荣二传手,这是正常的做法吗?

Question: Is it normal, and acceptable practice to have boilerplate reducers that function merely as glorified setters to the redux store state?

这是正常现象,但不一定正确.这就是我们代码库最初增长的方式-我们甚至有一些标准来将我们的操作命名为RESET_...SET_...REMOVE_...ADD_...UPDATE...等.这似乎可以工作一段时间,直到碰到我们需要多个reducer来根据一个动作进行更新的情况.

It is normal, but not necessarily correct. This is how our codebase grew initially - we even had standards to name our actions as RESET_..., SET_..., REMOVE_..., ADD_..., UPDATE... etc. This seemed to work for a while, until we bumped into cases where we needed multiple reducers to update according to a single action.

您的动作将以以下两种方式之一发展(或同时发生)

You actions will evolve in one of these 2 ways (or both)

  1. 连续发送多个操作(您必须使用类似

  1. Dispatch multiple actions in a row (you must use a library like redux-batch-actions if you want to dispatch multiple actions in a row). We chose not to use this, because it's cumbersome and didn't feel like it scaled very well as our codebase grew in size.

重命名您的操作,使其在不同的化简器上更加通用且可重复使用.这就是我们最终要做的.作为设定者和获取者采取行动是很麻烦的.丹·阿布拉莫夫(Dan Abramov)等人表达了他们的观点,即Redux Actions应该是events(对已经发生的事情的描述),而不是instructions(对应该发生的事情的描述).我所工作的团队对此表示同意,而我们已经摆脱了以传教士为代表的行动方式.关于Redux的新版本,对此早有很多争论.

Rename your actions to be more generic and re-usable across different reducers. This is what we ended up doing. Having actions as setters and getters was cumbersome. Dan Abramov and others has expressed their opinion that Redux Actions should be events (a description of a thing that has happened), rather than instructions (a description of a thing that should happen). The team I work on agreed with this, and we've moved away from the setters-style of actions. There was much debate about this earlier on when Redux was new.

在场景1 中,您可能会执行以下操作:

In scenario 1, you might do something like this:

// 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:
    /* ... */

如果您不使用批处理操作就走这条路,那绝对是一场噩梦.每个调度的事件将重新计算状态,并重新呈现您的应用程序.这很快就消失了.

If you go down this path without using Batch Actions, it's an absolute nightmare. Each dispatched event will recompute state, and re-render your app. This falls apart very quickly.

// 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));

在上面的代码中,您调度了一个将学生从班级中删除的动作,然后该应用重新渲染.如果您打开了成绩页面,并且可以看到学生的成绩,但是学生被删除了,您很有可能会在学生名册缩减器中引用状态以获取学生信息,并且可能/将引发JS错误.坏消息.您有undefined学生的成绩吗?我本人也遇到了这样的问题,这是我们转向下面的选项2的动力的一部分.您会听到称为中间状态"的这类状态,它们是有问题的.

In the above code, you dispatch an action to remove the student from class, and then the app re-renders. If you have a grades page open, and you can see the students grades, but the student is removed, you're very likely going to reference state in the student roster reducer to grab the student info and that can/will throw a JS error. Bad news. You have the grades for a student of undefined?! I ran into issues like this myself, and it was part of our motivation for moving to option 2 below. You'll hear about these kinds of states called "intermediate states" and they're problematic.

在场景2 中,您的代码可能看起来像这样:

In scenario 2 your code might look more like this:

store.dispatch(expelStudent(student));

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

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

使用上面的代码,通过操作将学生驱逐出局,并且一步一步从所有减速器中删除他们的数据.这样可以很好地扩展,并且您的代码库反映了您每天都会谈论的与您的应用程序相关的业务术语.从业务逻辑的角度来看,您还可以对多个事件执行相同的状态更新:

With the code above, the student is expelled via the action, and their data is removed from all reducers in 1 step. This scales nicely and your codebase reflects the business terms related to your app that you would talk about day-to-day. You can also perform the same state updates for multiple events if it makes sense from a business logic perspective:

case EXPEL_STUDENT:
case STUDENT_DROPPED_OUT:
case STUDENT_TRANSFERRED:
    /* remove student info, all actions must have action.payload.student */

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

Question: Is it normal for a redux application to rely extensively on thunk'ed action creators, and rarely to fire a standard object action directly?

是的.一旦您需要在动作创建者中从商店中获取一些数据,就必须将其变成笨拙的东西.恶作剧非常普遍,应该是redux库的一部分.

Yes definitely. As soon as you need to grab a little piece of data from the store in an action creator, it has to become a thunk. Thunks are very common, and should have been part of the redux library.

随着我们的重击变得越来越复杂,它们变得混乱并且难以理解.我们开始滥用诺言和回报价值,这很麻烦.测试它们也是一场噩梦.您必须模拟所有内容,这很痛苦.

As our thunks grew in complexity, they got confusing and difficult to easily understand. We started to abuse promises and return values and it was taxing. Testing them was also a nightmare. You have to mock out everything, it's painful.

为解决此问题,我们引入了 redux-saga . Redux-saga可以使用> redux-saga-test-plan redux-saga-test-engine (我们使用测试引擎,并根据需要做出了贡献).

To solve this problem, we pulled in redux-saga. Redux-saga is easily testable with libraries like redux-saga-test-plan or redux-saga-test-engine (we use test-engine and have contributed to it as needed).

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

We aren't 100% sagas, and don't aim to be. We still use thunks as needed. If you need to upgrade your action to be a little smarter, and the code is very simple, there's no reason why you shouldn't upgrade that action to a thunk.

一旦动作创建者变得足够复杂以至于需要进行一些单元测试,redux-saga可能会派上用场.

As soon as an action creator gets complex enough to warrant some unit testing, redux-saga might come in handy.

Redux-saga确实有一个粗略的学习曲线,并且一开始感觉很奇怪.手动测试Sagas太痛苦了.很棒的学习经验,但是我以后再也不会做.

Redux-saga does have a rough learning curve to it, and feels quite bizarre at first. Testing sagas manually is miserable. Great learning experience, but I would not ever do it again.

问题:redux是用于全局状态存储的正确工具吗?是否存在替代方案,其行为更类似于react的内置this.state,允许全局应用程序状态通过无状态的react组件传播,并通过集中的配电盘"从整个应用程序进行更新,而无需看似无休止的网络.采用redux附带的样板,常量和switch语句?

Question: Is redux the correct tool to use for a global state store? Are there alternatives out there that behave more akin to react's built-in this.state, allowing a global application state to be propagated through stateless react components, and updated from throughout the application via a centralized 'switchboard', without the seemingly endless web of boilerplate, constants and switch statements that come with adopting redux?

MobX -我从人们那里听到了有关它的好消息谁对Redux有相同的抱怨(样板太多,文件太多,所有内容都已断开连接)我自己并没有使用过它,也没有使用过它.您很有可能比Redux更喜欢它.它解决了相同的问题,因此,如果您实际上更喜欢它,那么值得进行切换.如果您要长时间处理代码,那么开发人员的经验非常重要.

MobX - I've heard good things about it from people who have your same complaints about Redux (too much boilerplate, too many files, everything is disconnected) I don't use it myself and have not used it, though. There's a good chance that you'll enjoy it more than Redux. It solves the same problem, so if you actually enjoy it more then it may be worth the switch. Developer experience is very important if you're going to work on code for a long time.

我对Redux样板软件没什么好感的.我工作的团队制作了宏来搭建创建新动作的样板,并且我们已经进行了很多测试,因此我们的Redux代码非常可靠.一旦使用了一段时间,就可以内部化样板,而且不会很累.

I'm okay with the Redux boilerplate and whatnot. The team I worked on has made macros to scaffold out the boilerplate of creating new actions, and we have lots of tests in place so our Redux code is pretty solid. Once you work with it for a while, you internalize the boilerplate and it's not as exhausting.

如果您长期坚持使用Redux,并且足够聪明,可以采用 在redux上实现流量 ,这对于长期的可维护性来说是一个巨大的胜利.全类型的redux代码使用起来非常好,尤其是对于重构.重构一个reducer/actionCreator很容易,但是忘记更新单元测试代码.如果您的单元测试涉及流程,它将抱怨您立即错误地调用了一个函数.太好了.

If you do stick with Redux long term, and are savvy enough to adopt flow on top of redux it's a huge win for long-term maintainability. Fully-typed redux code is amazing to work in, especially for refactoring. It's so easy to refactor a reducer/actionCreators, but forget to update unit test code. If your unit tests are covered by flow, it's going to complain that you're calling a function incorrectly immediately. It's wonderful.

引入Flow是克服复杂的障碍,但值得.我没有参与初始设置,但我认为将其引入代码库变得更加容易,但是我认为这将需要一些学习和时间.值得.绝对值得100%.

Introducing Flow is a complex hurdle to get over, but well worth it. I wasn't involved in the initial set up, and I assume it's gotten easier to introduce to a codebase, but I'd imagine that it will take some learning and hours. Worth it though. Definitely 100% worth it.

问题:一个人可以合法地(实际上,不只是公然地证明一个观点)将redux与一个减速器一起使用吗?

Question: Could one legitimately (in all seriousness, not just blatantly demonstrating a point) use redux with precisely one reducer?

您肯定可以,它可以用于小型应用程序.对于一个较大的团队来说,这将无法很好地扩展,并且重构似乎将成为一场噩梦.将商店分成单独的减速器可以使您隔离责任和关注点.

You definitely could, it could work for a small app. It wouldn't scale well for a larger team, and refactoring seems like it would become a nightmare. Splitting the store up into separate reducers lets you isolate responsibilities and concerns.

这篇关于在Redux中使用样板动作和减速器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆