白细胞酯酶阳性是什么| 23是什么意思| glu是什么意思| 化妆品属于什么行业| 心脏上有个小洞是什么病| 五年生存率是什么意思| 秦始皇的真名叫什么| 吹风扇感冒了吃什么药| 秋葵对痛风有什么好处| 山东的简称是什么| 沙龙会是什么意思| 堪忧是什么意思| 决明子是什么| 即使什么也什么| 什么是灰指甲| 尿蛋白是什么| 手肘发黑是什么原因| 宝宝喝什么奶粉好| 一念之间什么意思| 无花果是什么季节的水果| 刑冲破害是什么意思| 魇是什么意思| 手抖是什么情况| 岁月的痕迹是什么意思| 看望病人送什么花| 宋朝前面是什么朝代| 吃栗子有什么好处| 乔迁新居送什么礼物| 三月一日是什么星座| 小孩为什么会细菌感染| 电子厂是做什么的| 西瓜虫吃什么| 镪水池是什么| 一天两包烟会导致什么后果| 7.17是什么日子| 一什么三什么的成语| 肌钙蛋白高说明什么| 风信子的花语是什么| 大蒜不能和什么一起吃| 为什么有的女人欲太强| 胃在什么位置| 胃胀腹胀吃什么药| 耳朵后面痒是什么原因| 间接胆红素高是什么意思| 口真念什么| 糖醋排骨是什么菜系| 年柱将星是什么意思| 烤鱼一般用什么鱼| 靳东妹妹叫什么名字| 痛风能吃什么菜谱大全| 什么的白云| 里正相当于现在什么官| 心脏消融术是什么手术| 检查耳朵挂什么科| 黑眼圈是什么原因引起的| 排尿困难吃什么药好| 滑膜炎吃什么药最好| 不明觉厉什么意思| 国家三有保护动物是什么意思| 胃寒喝什么茶暖胃养胃| 抗甲状腺球蛋白抗体高是什么意思| 爱是什么东西| 鼻炎吃什么药见效快| 肛瘘不治疗有什么后果| 本科专科有什么区别| 氟哌酸又叫什么名字| 上火喝什么比较好| 行驶证和驾驶证有什么区别| 心口疼是什么原因| 吃什么可以补钙| 腹透是什么意思| 8月6号是什么星座| 为什么叫汉族| 痣的位置代表什么| et是什么| 男性解脲支原体是什么病| 白配什么颜色好看| 梦泪什么意思| 日益是什么意思| 两点是什么时辰| 什么叫刺身| 感冒喝什么汤| 老卵上海话什么意思| 月经量少发黑是什么原因| 长智齿是什么原因引起的| 苏东坡属什么生肖| laura是什么意思| 肩膀痛挂什么科| 晟怎么读音是什么| 不检点是什么意思| 为什么会有扁桃体结石| 洋葱为什么会让人流泪| 吕布的马叫什么名字| 真狗是什么意思| 望穿秋水的意思是什么| 彩蛋是什么意思| 身体逐渐消瘦是什么原因| 什么是考生号| 什么蛋营养价值最高| 失眠去药店买什么药| 结节有什么症状| 枸杞什么季节成熟| 水瓶是什么星座| 指导员是什么级别| 孕酮低是什么意思| 阿莫西林治疗什么| 喝什么解辣| 脚凉是什么原因造成的| 经辐照是什么意思| 为什么会子宫内膜增厚| 紫字五行属什么| 自由职业可以做什么| 上火吃什么最快能降火| 控线是什么意思| 红豆生南国什么意思| 孕妇吃什么长胎不长肉| 结晶体是什么意思| 结石是什么原因造成的| 时年是什么意思| 品牌主理人是什么意思| 肠胃不好喝什么奶粉好| 点卯是什么意思| 脑浆是什么颜色| 中医讲肾主什么| 推拿是什么| 气滞血瘀是什么意思| 二阴指的是什么| 糖耐是什么| 属牛的和什么属相最配| 毋庸置疑什么意思| 追什么| 嗤笑什么意思| 碳13和碳14有什么区别| 什么叫胆固醇| 胃疼适合吃什么食物| 太形象了是什么意思| 什么是小男人| 黄鼠狼最怕什么| 宫颈癌吃什么好| 宝宝照蓝光有什么副作用| 最近老是犯困想睡觉是什么原因| 喝姜粉有什么好处| 正常人吃叶酸有什么好处| 一丘之貉是什么意思| 止咳平喘什么药最有效| 事不过三是什么意思| 胡萝卜什么时间种| 鸡婆什么意思| 晚上九点半是什么时辰| 马是什么牌子的车| 梦见买狗是什么意思| 减肥晚上吃什么合适| 激素六项什么时间查最好| 什么是缓刑意思是什么| 属猪的护身佛是什么佛| 月子期间可以吃什么水果| 消化不良吃什么药最好| 常州有什么特产| gap是什么意思| t什么意思| 狻猊是什么| 96是什么意思| 一什么不什么| 人乳头瘤病毒16型阳性是什么意思| 脸发红发烫是什么原因| 疯癫是什么意思| 沉香是什么| 阿斯伯格综合症是什么| 恋足癖是什么意思| 吃什么东西会长胖| 超敏c反应蛋白高说明什么| 冷暴力什么意思| 月经颜色暗红色是什么原因| ctm是什么意思| 吃什么有助于排便| 小孩子手脚脱皮是什么原因| 缺钾吃什么食物补得最快| 尿蛋白是什么| 六月一日是什么星座| 洋酒是什么酒| 大便出血什么原因| 11月1日什么星座| 人潮涌动是什么意思| 高大的什么| 霉菌性阴道炎是什么引起的| 鸠是什么鸟| 亮晶晶的什么填空| 梦见找孩子什么预兆| 楠字五行属什么| 火字旁的字有什么| 养成系是什么意思| 梦到丢了一只鞋是什么意思| 婧字五行属什么| 胆囊小是什么原因| 嘴巴苦是什么原因引起的| 怀孕什么时候打掉最好| 腱鞘炎是什么| 邮政编码有什么用| 一个壳一个心念什么| 在圣是什么生肖| 柠檬不能和什么一起吃| 一个日一个处一个口念什么| 行尸走肉什么意思| 左上腹疼是什么原因| 什么是硬盘| 奔是什么生肖| 瑶浴是什么意思| 什么鱼没刺| 有黄鼻涕吃什么药| 敦伦是什么意思| 头发掉的严重是什么原因| 查激素水平挂什么科| 命运多折 什么生肖| 心慌是什么感觉| 五官端正是什么意思| 老人家头晕是什么原因| 颞下颌关节炎吃什么药| 血糖高的人吃什么好| 脚麻是什么原因造成的| 良善是什么意思| 胃烧心是什么症状| 冠心病是什么| 哮喘吃什么药最有效| 清酒是什么酒| 梦到和男朋友分手是什么征兆| 什么是种草| 胰腺是什么器官| 潘多拉是什么意思| 什么是次数| 前列腺增大是什么原因| 中国梦是什么意思| 有胃病的人吃什么最养胃| 嗳是什么意思| 眼压高用什么药| 11月8日是什么星座| 属牛的本命佛是什么佛| 家里为什么不能放假花| 龟头责是什么意思| 血管检查是做什么检查| 红痣是什么原因引起的| champion什么意思| 免疫比浊法是什么意思| 大麦茶是什么做的| 怨妇是什么意思| 1969属什么生肖| 天秤座是什么性格| 乙肝病毒携带者有什么症状| 长痘痘用什么药| 脾气虚吃什么中成药| prich是什么牌子| doneed是什么牌子| 张良属什么生肖| 中二病是什么意思| 吃什么东西会误测怀孕| 呕吐挂什么科| 梦见怀孕的女人是什么意思| 胎盘吃了对身体有什么好处| 声东击西是什么意思| 韧带拉伤吃什么药| hpv是什么病严重吗| 什么人不能吃狗肉| 1957年属什么| 四菜一汤是什么意思| 姨妈可以吃什么水果| 百度

Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability

This is a post in the Idiomatic Redux series.


Opinions on the benefits and use cases of thunks and sagas with Redux

Intro ??︎

I've spent a lot of time discussing Redux usage patterns online, whether it be helping answer questions from learners in the Reactiflux channels, debating possible changes to the Redux library APIs on Github, or discussing various aspects of Redux in comment threads on Reddit and HN. Over time, I've developed my own opinions about what constitutes good, idiomatic Redux code, and I'd like to share some of those thoughts. Despite my status as a Redux maintainer, these are just opinions, but I'd like to think they're pretty good approaches to follow :)

As an initial caveat: I've accumulated a lot of knowledge about Redux, but like all of us, what I know has limits. I've primarily used Redux in practice as part of one application in a specific environment. There's many things I've read about but only used at basic levels, like client-side routing, functional programming, unit testing, and "scaling" applications. What I know is a mixture of practical experience from my own application, and various amounts of theory from what I've read. So, I have a pretty good idea what I'm talking about, but I'm not an absolute expert on everything.

Looking around at the React and Redux community, there's a lot of people out there with way more experience than me. I'd also say most of them are much deeper thinkers than I am. I respect these people. I respect them a lot. People like Dan Abramov, who invented Redux and works on the React team. Or Leland Richardson, who helped build Enzyme, the most popular library for unit testing React components. Or Francois Ward, who's probably forgotten way more about unit testing and functional programming than I'll ever know. Or Em Smith, who has plenty of experience using Redux in a large-scale production application.

I'm going to do something really stupid and publicly disagree with them :)

Since this is going to get rather verbose, I'll summarize things up front.

TL;DR: ??︎

The redux-thunk and redux-saga libraries are the most widely-used libraries for "side effects" in Redux. Both provide a place to make AJAX requests, dispatch multiple actions, access the current store state, and run other complex logic. redux-thunk does this by allowing you to pass a function to dispatch(), while redux-saga uses ES6 generators to execute asynchronous logic.

There's been a lot of recent statements arguing that thunks (and sagas) are bad and should almost never be used. As a result, I've seen developers confused and wondering what alternatives they have to implement a given feature.

The concerns being raised are valid, but to balance the discussion, I would argue that thunks are a useful tool in Redux applications, and that developers should not be scared to use thunks in their codebase.

With those thoughts in mind, let's dig into the discussions and see just what has been said about thunks.

The Problem at Hand ??︎

My inspiration for this post comes from several sources:

Dan Abramov - "Don't Use getState in thunks" ??︎

First, Dan Abramov wrote a Stack Overflow answer back in February, in which he said:

In general accessing state in action creators is an anti-pattern and you should avoid this when possible. The few use cases where I think it’s acceptable is for checking cached data before you make a request, or for checking whether you are authenticated (in other words, doing a conditional dispatch). Passing data such as state.something.items in an action creator is definitely an anti-pattern and is discouraged because it obscured the change history: if there is a bug and items are incorrect, it is hard to trace where those incorrect values come from because they are already part of the action, rather than directly computed by a reducer in response to an action. So do this with care.

Since then, I've seen several people get confused by that comment and asking (paraphrased), "I see that getState is available in thunks, but apparently I can't use it, so how can I do $TASK now?". Now, part of it is that people are interpreting "avoid this when possible" as "NEVER DO THIS", but the fact that Dan said it on Stack Overflow definitely pushes people to take it as gospel.

Leland Richardson - "Thunks/sagas are too powerful" ??︎

Second, Leland Richardson started a discussion on Twitter about whether thunks and sagas are a bad idea:

(controversial?) opinion: redux-thunk is too powerful and is a foot-gun. avoid in favor of more convention-based middlewares
i think redux-saga is basically a 1:1 mapping of redux-thunk. just as powerful. just as much of a foot gun.
... its redux thunk that allows you go dispatch crazy...

In another part of the thread, he clarified his thoughts:

the main thing i've seen is just that people get a little dispatch crazy, and end up...
dispatching a bunch of actions sequentially even in the same tick for a given action creator...
this feels really bad to me for a couple of reasons...
1. these usually end up being glorified setter methods. avoiding those is arguably one of the founding motivations of redux
2. in between dispatches, it likely results in an inconsistent store state, since...
...you're treating an action creater like a transaction, except that it's not
3. each dispatch is synchronous, and ends up in potentially a lot of re-rendering on the react side etc.
so overall, the root of it is not that redux-thunk is inherently bad...
...but it gives engineers the power to do pretty much whatever they want...

Shortly thereafter, he created a new middleware called redux-pack. The README for redux-pack says:

redux-pack is a library that introduces promise-based middleware that allows async actions based on the lifecycle of a promise to be declarative. Async actions in redux are often done using redux-thunk or other middlewares. The problem with this approach is that it makes it too easy to use dispatch sequentially, and dispatch multiple "actions" as the result of the same interaction/event, where they probably should have just been a single action dispatch. This can be problematic because we are treating several dispatches as all part of a single transaction, but in reality, each dispatch causes a separate rerender of the entire component tree, where we not only pay a huge performance penalty, but also risk the redux store being in an inconsistent state. redux-pack helps prevent us from making these mistakes, as it doesn't give us the power of a dispatch function, but allows us to do all of the things we were doing before. ...and ends up making an "action creator" nothing more than a "literally anything could be happening here" function...

During that same Twitter discussion, Dan Abramov made several comments about thunks:

it was literally the "I hope people will come up with something better" solution, didn't expect it to catch on so much

Good thread on the problems with Redux Thunk. Indeed “glorified setters” is a very common misuse of Redux.
If your actions creator names start with set* and you often call multiple in a row, you might be missing the point of using Redux.
The point is to decouple “what happened” from “how the state changes”. “Setter” action creators defeat the purpose, conflating the two.
They are also inefficient and may lead to inconsistent UI. Dispatching multiple times is an escape hatch and should be used sparingly.

[getState in thunks is] useful for checking assumptions are still valid after async response comes in

Francois Ward - "Using getState breaks 1-way data flow and obscures actions" ??︎

Next, in a recent chat in the Redux channel on Reactiflux, a user came in asking about Dan's "avoid getState in thunks" quote. Afterwards, Francois Ward, Jim Bolla, and I had an extended debate on the relative merits of accessing state in thunks and sagas. A few of the relevant comments:

[10:29 PM] Francois Ward: I don't think he's wrong. Your UI generates actions, an event log, that gets reduced to a state that is used to generate/update a new UI. The only reason to use the state in an action creator is to get the interim computations that a previous action did within an action, which essentially means you're using the reduced state to create more actions...which doesn't make a whole lot of sense and breaks the 1 way nature of the architecture.
It also means you're doing a lot more in the reducer than just reducing the action log to a state, breaking its semantic.
Finally, even if you needed it, when the state updates, the UI will update and you can get the new version in componentWillReceiveProps, from which you can dispatch the new actions with it, keeping the data flow.
[10:30 PM] Francois Ward: its a nice escape hatch when you REALLY need it, but its excessively rare.
[10:34 PM] Francois Ward: the flow is meant to be UI -> actions -> reducers -> UI, not UI -> actions -> reducers -> actions -> reducers -> UI

[10:42 PM] Francois Ward: So, given a fully functioning UI with a given state (overloaded term here: I mean the props and context), you would be unable to generate a valid event log with this architecture.
[10:42 PM] Francois Ward: your UI is now tightly coupled to your reducer.
[10:43 PM] Francois Ward: it can't behave without a computed store.
[10:43 PM] Francois Ward: looking at the UI and the event log, you also can't tell where data came from.

[10:53 PM] Francois Ward: I said it already: whatever data you need to generate the computed payload of your action (which is a side effect of the UI when you think about it), needs to come from the UI.

[11:01 PM] JimBolla: I don't like the idea that I have give my UI extra data just to pass along to action creators. Components should only be responsible for rendering UI. Application behavior should be in the app layer. IMO action creators should be as self contained as possible. I want as much of my apps behavior out of React as possible. The UI is always the layer most likely to get wholesale replaced.
[11:12 PM] JimBolla: Consider this... an action creator that needs 4 pieces of data, some of that data lives in the store. There are 5 components that might call that action creator. Given your suggestion, each component then has to select all the data needed to pass to that action creator IF it calls it. What if selecting that data is expensive and that chance that action creator is called is low? Now you're needlessly slowing your app. What if a new requirement dictates that action creator now needs an addition piece of data that lives in the store. Now you have to go update all the components to ensure they pass the additional parameter.
[11:28 PM] JimBolla: My goal is to get as much code out of the components as possible, this includes shuttling parameters.

Em Smith - "thunks lead to duplicate async logic" ??︎

As I was working on this post, Em Smith published You may not need to thunk. In it, she argues that thunks are overused, as well as giving us less info about why things happened in the system:

By using thunked action creators, we start passing around functions throughout our application, which means that our actions can no longer easily be serialised, which impedes the ability to log the actions and complicates debugging, to paraphrase Mark Erikson:

The idea is that we aren’t seeing what logic that led up to the action being dispatched, as opposed to dispatching an action that acts as a “signal” to kick off the more complex logic.

By not having this initial action logged, we don’t know why we started to do something.

She also argues that using them for async requests results in painful duplication of request handling logic:

... most likely your application talks to an API at multiple points, across multiple files, which means every action creator is duplication the logic to perform API requests and connect them to your reducers to persistent different states in the store. ... [this action], in a piece of custom middleware, is transformed into a different set of actions. This centralises all the logic for talking to the reddit API in our application, and gives us actions which describe what we’re wanting to do, rather than doing a complicated flow of logic.

Reddit - "thunks aren't good for decoupling" ??︎

Finally, there was a recent Reddit thread on "fat containers vs fat thunks". In that thread, someone commented on reusability:

It's all dandy until you need to decouple your now big app into reusable sub-apps. Then you realize that your user profile actions know too much - about which action should be dispatched for errors, modals, auth etc. I am looking into building easy decoupled redux apps and there is not much to do besides some form of DI

Summing Up the Problems ??︎

Going over all these comments, I see a variety of major themes being raised:

  • Thunks and sagas let you run arbitrary code that can do anything
  • Using store state as a source of values for actions can obscure where the data came from
  • Multiple dispatches cause excess re-rendering
  • Multiple dispatches that are supposed to go together "transaction"-style could be interrupted, leaving the application in an unexpected state
  • Accessing store state makes testing harder (as does composing thunks together)
  • Accessing store state "breaks uni-directional data flow"
  • Thunks are too coupled and are bad for scalability
  • Using thunks for async requests results in duplicated logic, and potentially less visible info in the dispatched action trail

That's... a lot of different complaints about thunks. As I said earlier, almost all of those are valid statements and concerns.

So, does that mean we should go uninstall redux-thunk en masse, never to dispatch a function again? Not so fast :) First, time to take a detour to look at the thoughts of a couple people who definitely much deeper thinkers than I am. (Admittedly, that category includes a lot of people...)

Abstractions and Power ??︎

At React Europe 2016, Cheng Lou gave an incredible talk entitled "On the Spectrum of Abstraction". That talk is available for viewing here, and I'd encourage you to take the time to watch it. It's only a half-hour, but it is mind-blowing. It's worth re-watching two or three times, to let the ideas sink in. (For those who are interested, I wrote down a transcript/notes of the talk here).

I found a good summary of Cheng Lou's talk, which I'll quote here for reference:

Cheng's talk provided a useful way to evaluate technology choices in different contexts by placing them on a spectrum of abstraction and understanding the trade-offs that come with choosing technologies at different points on that spectrum. The "spectrum of abstraction" refers to the fact that some technologies solve very specific problems (eg. The clock app on a phone) and others are much more abstract but are useful for a broader class of uses (eg. a Promises library for JavaScript).

Cheng framed it as a kind of optimization problem to minimize the overall cognitive costs of the codebase in order to satisfy an evolving set of use cases. On the one hand, choosing unnecessary abstractions imposes a cognitive cost because of the gap between the abstraction and the problem being solved. On the other hand choosing too many problem-specific tools imposes other costs.

There was an interesting look at the trade-offs around the power that different abstractions have. eg. Declarative systems for specifying things limit the developer's freedom but often enable useful tooling and optimizations because of those constraints.

Similarly, Brian Lonsdorf (aka "DrBoolean"), recently tweeted:

The more you limit a system, the simpler it will be to understand. Less possibility = less complexity. Avoid power, embrace constraints.

In the presentation, the various levels of abstraction are represented as a tree. Leaf nodes represent specific use cases, and tools that only cover one or two leaf nodes are "more useful" and "less powerful". Nodes higher in the tree represent more abstract tools that cover a wider range of use cases, and are "less useful" and "more powerful". Per the presentation, the terms "more/less powerful" and "more/less useful" are not comparisons of whether a thing is good or bad. They are simply descriptions of how concrete and focused vs how abstract and broad a thing is.

Assuming I've understood Cheng Lou's terms correctly (which is still up for debate), I believe we can say that:

  • Thunks are more powerful, and thus higher in the tree of abstraction, because they can be used for every conceivable use case
  • Sagas are both more and less powerful than thunks, and so probably about the same level in the tree. Their declarative usage is somewhat more specific, so in that sense they'd be a little lower. On the other hand, given that sagas can respond to dispatched actions, not just dispatch them, in some ways sagas are more powerful and cover more use cases.
  • A middleware like redux-pack is very useful and much less powerful, and thus a leaf on the tree. This is because it's intended for a very specific use case: tracking and dispatching actions for async requests.

With those thoughts in mind, let's look at the concerns about thunks in further detail.

Answering the Concerns ??︎

I think we can group up some of these concerns into categories. From there, we can talk about what these concerns mean, how serious they are, and what solutions might be available.

Power ??︎

Related concerns:

  • Thunks and sagas let you run arbitrary code that can do anything

This description is 100% correct. Thunks and sagas both effectively hand you a bucket and a shovel and say "There's the beach, go have fun making sand castles!". However, the real question is whether this is actually a bad thing, a good thing, or neutral.

I would say that the ability for thunks to do anything is absolutely a good thing. A middleware like redux-pack or one of the many other "promise lifecycle"-type middlewares by definition only works for certain use cases - dispatching a sequence of actions representing the progress of an async action. If that use case is what you're trying to solve, great, you're in luck! If not... well, you need something with more flexibility.

(As an interesting observation: Cheng Lou's talk seems to suggest that more powerful tools generally involve more abstraction and are usually harder to understand. In a somewhat ironic reversal, redux-pack, a less powerful and more useful tool, takes a bit more work to understand than redux-thunk, which is incredibly simple in both concept and implementation. I'm not sure this actually means anything important, just an interesting thought.)

Coupling, Testing, and Scalability ??︎

Related concerns:

  • Thunks are too coupled and are bad for scalability
  • Accessing store state makes testing harder (as does composing thunks together)

There's definitely some validity to these concerns, but I feel like the original comments that I saw are over-stating the problems.

Part of the issue here is that Redux's single store and middleware pipeline is great for application-wide concerns, but comes with the tradeoff that full encapsulation and composition of logic becomes correspondingly harder. Full encapsulation and Lego-like reusability of logic and components in Redux isn't impossible, but it does take a lot of extra work to accomplish. There's no easy definitive solution for that yet, although there's a lot of ongoing community research and experimentation with approaches.

To specifically address some of the concerns raised by the Reddit comment earlier: most applications don't need to be divided up into "reusable sub-apps", and even if that is a use case you need to deal with, it doesn't necessarily imply that "your $FEATURE actions know too much about $OTHER_FEATURE". That might be the case, but that's really a question of how you've architected your app and written your code, not an inherent problem with thunks.

Now, it is true that if you're composing multiple thunks together (ie, bigComplexThunk() itself dispatches smallerThunk1(), smallerThunk2(), and smallerThunk3()), those are now pretty tightly coupled. However, I would say this is not a huge problem, and not a reason to stop using thunks. Sure, the platonic ideal of "good code" may be perfectly decoupled functions all unit testable in isolation, but if a codebase doesn't reach that ideal, it won't kill the application. Looking at some of my own code that composes thunks together, most of the "smaller" pieces are decently testable themselves, and while I definitely lack experience doing unit testing overall, I could probably write tests for some of the "bigger" thunks.

As for the complaints about usage of getState being a problem in thunks (and its equivalent select() in sagas): Yeah, I see the concern. In both cases, the code accessing state would would probably count as "impure" code in that the data wasn't directly passed in as arguments, and we've certainly learned that pure functions are easier to test. However, unlike reducers, action creators are not required to be 100% pure functions. In addition, the fact that the state is injectable should make testing somewhat easier. Yes, you do need to have a representative store state prepared to test this code properly, but that can now be used either by using something like redux-mock-store, or by directly passing a fake getState function into the thunk (or the equivalent approach for passing a state into a saga).

Looking at these concerns, I would point to the phrase "The perfect is the enemy of the good". Sure, making our code more testable and more decoupled is almost always a good thing, but code that isn't 100% perfect in that regard can still be entirely usable for the intended purpose.

State Tracing ??︎

Related concerns:

  • Using store state as a source of values for actions can obscure where the data came from
  • Using thunks results in potentially less visible info in the dispatched action trail

I sorta see where these complaints are coming from, but I don't agree that they're meaningful problems.

Re-reading the quotes earlier in the article, the best I can make out of the "obscure the data" argument is that if a value in the store happens to be wrong, putting it into an action just perpetuates the problem. And, uh... yes? Why is that bad, exactly? If it's wrong, and it's put into a dispatched action, you should be able to see the value during development and debugging, glance at the code that dispatched the action, see it came from the store, and track back through the previous actions to see what went wrong. I'm also not exactly sure why this would be any different than the more general case of "the logic that led to dispatching an action passed along a bad value", and I would almost think that knowing the value came from the store originally would make it easier to trace the flow (as opposed to a value coming from a random chain of promises or something). Besides, one of Redux's selling points is that actions are usually only dispatched in a couple places, so a search for the action type should point you to where it came from pretty quickly.

As for the "less visible info" concern: I think the idea here is that "signal" actions used to trigger listening sagas effectively act as additional logging for the application, giving you that much more of an idea what was going on internally. I suppose that's true, but I don't see that as a critical part of trying to debug an application. Not everyone uses redux-saga, and even for those who do, you should probably have other logging set up in your application. Looking for "signal" actions is probably helpful, but not having them available just doesn't seem like a deal-breaker to me.

Multiple Dispatching ??︎

Related concerns:

  • Multiple dispatches cause excess re-rendering
  • Multiple dispatches that are supposed to go together "transaction"-style could be interrupted, leaving the application in an unexpected state

Both of these concerns are correct. I do have to throw a small caveat in here. React-Redux's connect function does a lot of work to ensure that the "plain" components only re-render when they actually have new props, either from the parent component or from mapState. If there are multiple dispatches, most likely a given component's mapState function will return the same results as before, unless the dispatched action directly resulted in changes to data that this component cared about.

Let me bring out a semi-concrete example. In my own app, I've built up a number of "primitive" actions that are often reused as part of larger tasks, such as ENTITY_CREATE, ENTITY_UPDATE, EDIT_ITEM_START, MODAL_SHOW, and MODAL_CLOSE. For a specific feature, I have a dialog that shows a list of items. That list can itself pop up a second dialog to let the user enter values and create a new item. I have a menu item that allows the user to jump straight to the "new item" dialog. The thunk attached to that menu item looks like this (names altered for clarity of the example):

export function showNewItemDialog() {
    return dispatch => {
        dispatch(showDialog("ItemManagement"));
        dispatch(showDialog("NewItem"));
        dispatch(requestItemsListFromServer());
    };
}

In that example, running the thunk will cause three dispatches to hit the store: showing the first dialog, showing the second dialog, and loading the data from the server.

Going back to the "re-rendering" question: yes, by default every call to dispatch will result in every connected component instance's mapState function being run. It's entirely likely that the majority of those calls are, in a sense, "wasted" effort, and so one way to theoretically improve performance is to minimize the number of dispatches. This is usually done by batching up dispatches, and there's a lot of ways to approach that idea:

  • Dispatching a single action that multiple reducers listen for and all independently respond to appropriately
  • Dispatching a "wrapper" action that a higher-order reducer unwraps, then runs each of the "wrapped" actions
  • Using a store enhancer to allow dispatching an array of actions, with a single notification
  • Using a store enhancer to debounce notifications
  • Using a middleware to debounce dispatches

Another way to improve perf is to use memoized selector functions inside of mapState to cut down on expensive work and ensure that cached values are returned if appropriate.

However, like all performance and optimization efforts, the real question is whether this is a meaningful performance problem in the first place. If you've got a CRUD app that's mostly sitting there doing nothing, then a couple extra dispatches probably aren't going to slow anything down. If you've got a real-time websocket-connected application spewing out hundreds of update events a second, the couple dispatches in this thunk or saga are going to be completely outweighed by all the data updates coming in. So sure, fewer dispatches is generally a good thing, but most of the time this is not going to be a performance bottleneck.

Looking at the second concern: yes, it's technically true that after each of the "steps" the app is sorta-kinda in an "intermediate" state compared to the desired end result. In this specific example, that's really not a problem, but I could see that there could be cases where that "intermediate" state is unexpected and breaks things.

Part of that is that a Redux store, state, and reducer are a kind of state machine, but probably not really a fully formally specified one. There's likely some ad-hoc assumptions encoded in the state tree. In my own app, I have some actions that are only intended to be dispatched while I'm actively editing an item. I don't have all of those actions set up with sanity checks in the action creators at the moment, because I'm relying on other parts of the app to ensure that the actions are only dispatched if appropriate (such as input fields or buttons being disabled unless isEditing is true). So yes, I could envision some unformalized assumptions being broken in some "intermediate" state, especially if there's some asynchronous work going on.

Still, in the end, I'm not worried about these concerns. If perf is an issue with dispatching, it can be handled with one of the various batched dispatch approaches. If your actions can result in some "unstable" states, you probably want to rethink how that's being handled anyway (or, apply the same batching concepts to implement "transactions").

Other Concerns ??︎

Related concerns:

  • Using thunks for async requests results in duplicated logic
  • Accessing store state "breaks uni-directional data flow"

Time for the last (and actually least) couple concerns.

Sure, if you've got a whole bunch of really complicated response processing logic, then it probably should be centralized. That said, I would guess that the vast majority of applications are going to be much simpler - make a request, dispatch an action, done. Not much to centralize there.

As for "accessing state breaking uni-directional data flow", I really don't agree with this at all. The idea of uni-directional data flow is that data updates are applied in one place, and propagate through the system from there. In a plain React application, that means callbacks passing an event up a component tree to an ancestor, calling setState(), and passing the new data back down as props. In a Redux app, that means that all the "write" logic is encapsulated in the root reducer function, the reducer is run in response to dispatched actions, and all connected components read the updated state from the store afterwards. This is in contrast to a typical Angular app that would use 2-way input binding to directly modify a model, or a Backbone app where every view can call this.model.set("someField", someValue) at any time.

As far as I can see, reading state in a thunk or saga does not break uni-directional data flow. The action is still dispatched, the reducer is still run, and the components still read the updated state. It's not like the thunk is directly modifying the state itself. I'll grant that it's not as strict as Elm, where every piece of data is passed down from view to view explicitly, but I'm not seeing a problem here.

Final Thoughts ??︎

As I said earlier, these concerns are generally valid observations that have been raised by some pretty smart people. I just don't think these are sufficient reasons to stop using thunks and sagas, stop accessing state in the process, or completely avoid multiple dispatches.

Dan Abramov recently tweeted about people over-interpreting "container" vs "presentational" components. In that tweet thread, he wrote:

It’s okay to convert a functional component to a class when you need lifecycles or state. That’s why React exists in the first place. Does it limit reuse? Sometimes maybe. Do you plan to reuse it? If you don’t know, don’t worry. You can always extract a pure part later.

I believe the same principle applies with thunks and sagas. Not every application is highly performance sensitive. Not every application needs centralized async request handling. Not every chunk of logic involves an async request with a predictable lifecycle of actions to dispatch. Not every application needs every last line of code to be 100% pure and perfectly unit testable. Not every application needs to be split into completely encapsulated, composable, and reusable pieces. Not every problem needs to be solved with a tool purpose-built for that exact use case.

As developers, it's our job to look at a problem, evaluate possible solutions, and pick the solution that best fits our use case. Sometimes that may be a hyper-specialized library written for this exact purpose. Other times, it might be a general-purpose tool that can be adapted for the job.

Ultimately, I still believe thunks and sagas help solve real problems, and are good tools to help build Redux apps. I encourage Redux users to make use of these tools wherever they are needed.

Further Information ??︎


This is a post in the Idiomatic Redux series. Other posts in this series:

什么是品牌 风热是什么意思 什么情况需要打狂犬疫苗 9月17日是什么星座 什么奶粉对肠胃吸收好
最贵的榴莲是什么品种 证候是什么意思 叶酸吃到什么时候 钢琴10级是什么水平 跖疣是什么东西
为什么胃酸会分泌过多 冰恋是什么意思 猴子的尾巴像什么 梦见皮带断了什么预兆 婴儿大便隐血阳性是什么意思
牙疼吃什么 混纺棉是什么面料 吃瓜子有什么好处 英特纳雄耐尔是什么意思 pwp是什么意思
胸痛应该挂什么科aiwuzhiyu.com 几又念什么hcv8jop8ns9r.cn 太平鸟属于什么档次hcv9jop6ns7r.cn 13年是什么年mmeoe.com 犀利是什么意思hcv8jop2ns7r.cn
集中的近义词是什么hcv8jop0ns9r.cn 子宫是什么样子图片hcv9jop3ns6r.cn 益生菌有什么作用hcv9jop1ns1r.cn 梦见殡仪馆是什么意思hcv9jop4ns1r.cn 小儿咳嗽吃什么药好hcv8jop0ns8r.cn
面肌痉挛是什么原因引起的hcv9jop3ns7r.cn 指甲凹凸不平是什么原因wuhaiwuya.com 什么叫艾滋病hcv8jop2ns3r.cn 什么的味道youbangsi.com 10月21日是什么星座hcv9jop1ns4r.cn
五七干校是什么意思hcv7jop6ns8r.cn 万艾可是什么hcv9jop4ns1r.cn 日本买房子需要什么条件hcv8jop1ns0r.cn 牙龈老是出血是什么原因引起的hcv8jop3ns0r.cn 匪夷所思是什么意思hcv9jop2ns2r.cn
百度