五指毛桃不能和什么一起吃| 9.25是什么星座| 备孕喝苏打水什么作用| 德国高速为什么不限速| 总是想睡觉是什么原因| 二建什么时候考试| 儿童拖鞋什么材质好| 挂绿荔枝为什么那么贵| 下午五点多是什么时辰| 霍金得了什么病| 鸟几念什么| 黑色的玫瑰花代表什么| 翅膀车标是什么车| 什么时候立冬| 经停是什么意思| 珍珠状丘疹有什么危害| 吃什么生发| 阴道黑是什么原因| 脉管炎吃什么药最好| 甲亢是什么原因引起的| 甲抗是什么原因引起的| 脚趾缝痒溃烂用什么药| 嘴苦是什么原因| chemical是什么意思| 冰点是什么意思| 老酒是什么酒| 肛裂是什么原因引起的| 六月八号是什么星座| 36朵玫瑰花代表什么意思| 鼻涕倒流到咽喉老吐痰吃什么药能根治| 清和是什么意思| 六月十一号是什么星座| 石斛什么价格| 蚊子喜欢咬什么血型| 什么原因引起尿路感染| 什么东西养胃又治胃病| 肾衰竭是什么症状| 别致是什么意思| 什么是尖锐湿疣| 鸡爪煲汤放什么材料| 尿黄尿味大难闻是什么原因| 粘米粉是什么粉| 依然如故的故是什么意思| 甲亢的症状是什么| 2009年什么年| 补办护照需要什么材料| 小苏打学名叫什么| 舌头白腻厚苔是什么原因| 6.29是什么星座| 总恶心是什么原因| 温居是什么意思| 为什么尽量不打免疫球蛋白| 吃什么补气血最快最好| 灌肠什么感觉| 晚上睡觉老做梦是什么原因| 静养是什么意思| 津字五行属什么| hdr模式是什么意思| 黄连治什么病最好| 上善若水下一句是什么| 无痛人流和普通人流有什么区别| 乙肝e抗体高是什么意思| 黑代表什么生肖| 老说梦话是什么原因| 为什么新疆人不吃猪肉| 分解酒精的是什么酶| 95年是什么命| 月经不调吃什么药好| 创客是什么意思| 胸口闷痛挂什么科| 吃什么会变胖| 梦见朋友结婚是什么意思| 蒙圈什么意思| 献血前需要注意什么| 彩超能检查出什么| 促销员是做什么的| 什么蘑菇有毒| 贫血吃什么食物好| 死于非命是什么意思| 什么是心率| 节节草有什么作用| 祉是什么意思| 小儿感冒吃什么药| 旗袍搭配什么鞋子好看| 菩提手串有什么寓意| 活佛是什么意思呀| 得水痘不能吃什么| 湿气重什么原因| 耳呜吃什么药最好| 香茅是什么东西| 微不足道的意思是什么| 梦到男朋友出轨了预示什么意思| 经常口臭的人是什么原因引起的| 黑科技是什么| 攻受是什么意思| 吃什么升血压最快| 佛法的真谛是什么| 梦见很多蜘蛛是什么意思| 迪化是什么意思| 什么叫ins风格| 狗狗咳嗽吃什么药| 属鼠的和什么属相最配| 什么是性瘾症| 外冷内热是什么症状| 麦昆牌子是什么档次| cbs是什么意思| 吃榴莲不能和什么一起吃| 被孤立的一般是什么人| 孕酮低什么原因造成的| 多多益善的益是什么意思| 艾滋病初期皮疹是什么样的| 中国最高军衔是什么| 妊娠纹是什么| 什么是激素| 表白墙是什么| 做流产手术需要准备什么东西| 口腔医学是干什么的| 闻所未闻是什么意思| 草莓什么季节| 叶公好龙告诉我们什么道理| 嘴唇薄的男人面相代表什么意味| 卵巢分泌什么激素| miniso是什么意思| 书字五行属什么| 失信是什么意思| 吃什么药死的快| 1990年1月属什么生肖| 04属什么生肖| se是什么| 睡觉天天做梦是什么原因| 肝结节是什么意思| 心电图hr是什么意思| 6月5号什么星座| 361是什么意思| 什么的绿毯| 六根清净是什么意思| 脚肿吃什么药| 炒菜什么时候放调料| 什么是癫痫| 兔唇是什么原因造成的| 悉如外人的悉是什么意思| 西游记告诉我们什么道理| 尿素氮偏高是什么原因| 群什么吐什么| 低聚木糖是什么| 同房时间短吃什么药| 女性尿路感染吃什么药| 舂米是什么意思| 口腔溃疡一直不好是什么原因| save是什么意思| 尿酸高早餐吃什么| 苦瓜为什么是苦的| 质变是什么意思| 免职和撤职有什么区别| 印泥用什么能洗掉| 逆时针揉肚子起什么作用| 经常生病是什么原因| 免疫力低下吃什么好| 什么是老赖| 蛇用什么呼吸| 扶山是什么意思| 甘油三酯高吃什么好| 男性解脲支原体是什么病| 月经量少发黑是什么原因| 柠檬吃多了有什么坏处| 16年属什么生肖| 淀粉酶是什么| 熬夜伤什么器官| 为什么家里蟑螂特别多| 峻字五行属什么| 小孩做ct对身体有什么影响| 军五行属什么| 为什么不能拜女娲娘娘| 胃不舒服吃什么食物好| 什么的虫子| 伤口不愈合用什么药| 孕期血糖高可以吃什么水果| 男人艾灸什么地方壮阳| 罗汉果有什么功效| 米虫长什么样| 梦见别人过生日是什么意思| 大油边是什么| 月经量突然减少是什么原因| 什么是gay| 身体虚弱打什么营养针| 稽留流产什么意思| 首鼠两端什么意思| 什么立雪| 颈椎用什么字母表示| 血小板分布宽度偏低是什么原因| 高嘌呤是什么意思| 宫颈那囊什么意思| 脚面疼是什么原因引起的| 狗和什么属相最配| 4.15是什么星座| 尿出红色的尿是什么原因| 肾外肾盂是什么意思| 为什么要当兵| 肾虚去医院挂什么科| 伏藏是什么意思| 送情人什么礼物最好| 什么样的女人不能娶| 鸡枞菌长在什么地方| 巧克力有什么功效与作用| 三个犬念什么| 去湿气吃什么食物| 什么叫人工智能| 小猫为什么一直叫| 牙龈流血是什么原因| 勃是什么意思| 康熙雍正乾隆是什么关系| r标是什么意思| 舌头上火是什么原因| 心季吃什么药| 高粱是什么粮食| 今天冬至吃什么| 皮疹和湿疹有什么区别| 魁罡贵人是什么意思| 阴毛变白是什么原因| 今年属于什么年| 什么叫精神病| 975是什么意思| 10月16日是什么星座| 肛门里面疼是什么原因| cst是什么意思| 女子胞指的是什么| 狮子吃什么| 三颗星是什么军衔| 一直想大便是什么原因| fsh是什么| 神经外科治疗什么病| 什么是厌食症| 什么是ok镜| 未成年喝酒有什么危害| 圆寂什么意思| 8月26日是什么星座| vaude是什么品牌| 什么叫根管治疗牙齿| 腺样体肥大有什么症状| 尿蛋白质阳性什么意思| 脚热是什么原因| 蛋皮痒痒是什么病| 眼花是什么原因| 4ever是什么意思| 一事无成是什么生肖| 陈皮有什么功效作用| 肌肉痛吃什么药| 推崇是什么意思| 胎记看什么科| 马眼是什么| 茶叶渣属于什么垃圾| 梦见桥断了有什么预兆| 总流口水是什么原因| 骨骼肌是什么| 凉栀是什么意思| 住院门槛费是什么意思| 氨曲南是什么药| 结婚纪念日送什么花| 丙肝阳性是什么意思呢| 什么属相不能带狗牙| 牙疼不能吃什么| 双修是什么意思| 金克什么| 碱吃多了有什么危害| 百度

Idiomatic Redux: The Tao of Redux, Part 2 - Practice and Philosophy

This is a post in the Idiomatic Redux series.


More thoughts on what Redux requires, how Redux is intended to be used, and what is possible 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 :)

This is a two-part series, intended to explain why specific Redux usage patterns and practices exist, the philosophy and intent behind Redux, and what I consider to be "idiomatic" and "non-idiomatic" Redux usage.

In Part 1 - Implementation and Intent, we looked at the actual implementation of Redux, what specific limitations and constraints it requires, and why those limitations exist. From there, we reviewed the original intent and design goals for Redux, based on the discussions and statements from the authors (especially during the early development process).

In Part 2 - Practice and Philosophy, we'll investigate the common practices that are widely used in Redux apps, and describe why those practices exist in the first place . Finally, we'll examine a number of "alternative" approaches for using Redux, and discuss why many of them are possible but not necessarily "idiomatic".

Table of Contents ??︎

Redux In Practice ??︎

There's a long list of common approaches and "best practices" that are used in Redux apps. Many of the frequent complaints about "boilerplate" involve these concepts. Let's walk through these practices and review the reasons why they're common and usually encouraged.

Actions, Action Constants, and Action Creators ??︎

These three concepts are probably the ones most commonly referred to in complaints about "boilerplate". I was going to cover each of these separately and in detail, but then I realized that there's already a well-written document that addresses why these concepts exist and why they're a good thing: the "Reducing Boilerplate" page in the Redux docs.

I'll quickly summarize the main points:

  • Actions: Describing updates as plain serializable action objects enables time-travel debugging and hot reloading
  • Action constants: Help with consistent naming conventions, make it easier to see what action types are used in an application, help prevent typos, and allow static analysis of code in IDEs
  • Action creators: Encapsulate logic around creating actions, reduce duplication, allow moving logic out of components, and act as an API.

As mentioned in Part 1, many of Redux's conventions are inherited from the original Flux Architecture concepts, and the Flux Actions and the Dispatcher section of the Flux docs does describe these ideas. My earlier post in this series, Idiomatic Redux: Why Use Action Creators?, also gives further thoughts on why use of action creators is a good practice.

There was also an interesting Twitter thread last year, where Dan responded to an article about "no action creators needed":

I wonder if it was better to not include action creators in Redux docs. People would come up with them anyway but wouldn’t blame Redux.
Separation between actions and reducers is the whole reason Redux exists. Read You Might Not Need Redux
... we failed to explain these things well enough that people see “dispatching” or “action creators” as scary
I don’t mean to blame you, the blame is on us. It means we failed to document and explain the library and concepts.
“Dispatching” is the only feature of Redux. That people try to hide from it means we didn’t explain why Redux exists well.
As also evidenced by hundreds of libraries that conflate reducers and action creators together as if actions were “local”.
Underlying all of it is, in my opinion, a basic misunderstanding of when to use Redux, and it’s our fault.

Plain object actions are a core design decision for Redux. Use of action constants and action creators is up to you as a developer, but both of those are derived from good software engineering principles like encapsulation and the ever-popular mantra of "Don't Repeat Yourself".

Defining Actions, Action Creators, and Reducers in Separate Files ??︎

As mentioned in the Redux FAQ on file/folder structure, Redux itself doesn't care at all how you organize your files. That's entirely up to you, and you should feel free to do whatever works best for your project.

Defining different types of code in different files is a natural pattern for a developer to follow. At a minimum, you'd probably want to write your Redux-related code in separate files from your React components. Reducers are their own thing, so it's reasonable to put them in one file. If you've chosen to use action creators in your project, those are a different kind of thing, so they would go in a second file. From there, you would need to make use of the same action type strings in both the action creators file and the reducers file, so it makes sense to extract them into a separate file that can be imported in both places. So, this approach isn't a requirement in any way, but it's a very natural and reasonable approach to follow.

The community-defined "ducks" pattern suggests putting action creators, constants, and reducers all together in one file, usually representing some kind of domain concept and logic. Again, Redux itself doesn't care if you do that, and it does have the benefit of minimizing the number of files that have to be touched if you update a feature.

To me, there are a couple conceptual downsides to the "ducks" pattern. One is that it guides you away from the idea of multiple slice reducers independently responding to the same action. Nothing about "ducks" prevents you from having multiple reducers respond, but having everything in one file somewhat suggests that it's all self-contained and not as likely to interact with other parts of the system. There's also some aspects of dependency chains and imports involved - if any other part of the system wants to import action creators from a duck, it will kind of drag along the reducer logic in the process. The reducers may not actually get imported elsewhere, but there's a dependency on that one file. If that doesn't bother you, feel free to use "ducks".

Personally, I lean towards a "feature folder"-type approach, where one folder contains the code for a given feature, but with separate files for each type of code. I previously wrote some of my thoughts in Practical Redux, Part 4: UI Layout and Project Structure.

Slice Reducers and Reducer Composition ??︎

This is based on the original Flux idea of having multiple "Stores" for different types of data. Also, in Flux, each Store registered itself with the Dispatcher, and each time an action was dispatched, the Dispatcher would loop through each Store to give them a chance to respond to the action.

Given the desire for Redux to store a variety of data in one tree, the straightforward approach is to have a top-level key or "slice" for each different category of data in the state tree. From there, the concern is how to initialize that state, and how to organize the logic for updating the state.

As discussed in the Structuring Reducers - Basic Reducer Structure section I wrote for the Redux docs, it's important to understand that your entire application really only has one single reducer function: the function that you've passed into createStore as the first argument. That root reducer is the only one that is required to have the (state, action) => newState signature. It's entirely possible to have that one function be directly responsible for initializing all the slices of the state tree and keeping them updated, but this once again leads us to basic software engineering principles: we split up large sections of code into smaller sections for better maintainability.

There's a natural mapping from having a Store class that manages some data and is notified when an action is dispatched, to having a plain function that is responsible for handling some data and is called when an action is dispatched. Since Flux Stores were turned into slices of state, it follows that the equivalent Redux function would be responsible for managing that slice of state. This results in a nicely recursive structure. Each slice reducer can have the same (state, action) => newState signature as the root reducer, and as far as a slice reducer is concerned, its state parameter is the entire state - it doesn't need to know that it might be a small part of a bigger tree, and could in theory be reused in another context. It's even possible, if perhaps unlikely, that a reducer used as a slice reducer in one application could be used as the root reducer in another application.

This pattern is a direct result of the transformation from Flux to Redux, and is absolutely the encouraged and idiomatic approach.

Switch Statements ??︎

Ah, the dreaded switch statement. For some reason, this is one of the most disliked aspects of typical Redux code, and I have yet to figure out why.

Reducers need to examine actions and use some kind of conditional logic to determine whether it's something they care about. You could use if/else statements, but those get repetitive pretty quickly, especially when you're only looking at a single field. A switch statement is simply an if/else that's focused on possible values for a single field, so it's the most straightforward approach for looking at the contents of action.type.

Switches and if statements are semantically equivalent, and so are lookup tables keyed by action constants. The Reducing Boilerplate docs page explicitly demonstrates how to write a function that accepts a lookup table of reducers to handle various cases, and this concept is also discussed in Structuring Reducers - Refactoring Reducers Example. (There are a couple structures that lookup tables don't handle well, such as running reducer logic in a switch's default case, or keying behavior off something other than the action.type field. Dan gave some examples in redux#1024.)

Despite that, somehow the Redux community seems to think that this is a wheel that needs to be re-invented, again and again and again. To expand on a tweet that I posted last year:

There's a classic SF humor story about a bulletin board of time travelers, and the first thing any new poster in the forum does is travel back to kill Hitler. The Redux equivalent: every new user writes a "build a reducer lookup table" function.

Switch statements are fine. If/else statements are fine. Lookup tables are fine. Just pick one and keep going :)

(As an interesting side note: a few months after Redux came out, someone wrote an article discussing how React+Redux is kind of like the classic Win32 WndProc function, including the use of switch statements. Interesting analogy, good article, and there was actually some good discussion in the HN comments.)

Middleware for Async Logic ??︎

This is another topic that's been covered in detail in existing documentation and posts. In particular, the Redux FAQ question on async logic and Dan Abramov's two Stack Overflow answers on why middleware are used for async behavior and how to handle async logic like timeouts explain things very well.

Summarizing the ideas: you can put your async logic directly into your components. However, for reusability, you probably want to extract that logic out of the components and into separate functions. Connected components potentially have access to dispatch, but can only access the state that has been extracted via mapStateToProps. Separate functions could only access dispatch or getState if the store is directly imported and referenced. That means that async logic that has been extracted into functions needs some way to interact with the store.

As discussed in Part 1, some Flux libs like Flummox had various forms of async handling built-in, but you were limited to whatever was included. For Redux, middleware was explicitly intended as a user-configurable way of adding any kind of async behavior on top of Redux.

Because middleware form a pipeline around dispatch and can modify/intercept/interact with anything coming through that pipeline, and are given references to the store's dispatch and getState methods, they form a loophole where async behavior can occur but still interact with the store during the process.

Thunks, Sagas, and Observables, Oh My! ??︎

There are dozens of existing libraries for managing side effects in Redux. That's because there's a lot of ways to write and manage asynchronous logic in Javascript, and everyone has different preferences and ideas on how to do so. As stated previously, you don't need middleware to use async logic in a Redux app, but it's the recommended and idiomatic approach.

From there, it's a question of what use cases you have, and what your preferences are for writing async logic. Side effects approaches can generally be grouped into five-ish categories. I'll link to the relevant sections of my Redux addons catalog, and point out the most popular libraries for each category:

  • Functions : use of plain functions as a general tool for whatever async logic you want to run. Popular choice: redux-thunk, which simply allows passing functions to dispatch that are then called and given dispatch and getState as arguments. Thunks are seen as the "minimum viable approach" for side effects with Redux, and are most useful for complex synchronous logic and simple async behavior (like fire-and-forget AJAX calls).
  • Promises: use of promises as arguments to dispatch, usually for dispatching actions as the promise resolves or rejects. Popular choices: redux-promise, a Redux middleware version of Andrew Clark's original promise behavior from Flummox, and redux-pack, a recent library from Leland Richardson that tries to add more convention and guidance to async transactions.
  • Generators: use of ES6 generator functions to control async flow. Popular choice: redux-saga, a powerful Redux-oriented flow control library that enables complex async workflows via background-thread-like "saga" functions.
  • Observables: use of observable / Functional Reactive Programming libraries like RxJS to create pipelines of async logic. Popular choices: redux-observable and redux-logic, which are both based on RxJS, but provide different APIs for interacting with RxJS and Redux.
  • Other: assorted other approaches for async behavior. Popular choice: redux-loop, which allows reducers to return descriptions of side effects, similar to how the Elm language works.

There's a particularly good comparison of many Redux side effect libraries at What is the right way to do asynchronous operations in Redux?.

All of these are viable tools and approaches for managing side effects in Redux. The two most popular at this point are thunks and sagas, but it really is up to you to decide what you want to use. (For what it's worth, I use thunks and sagas, but haven't needed anything else.)

Dispatching 3-Phase Async Actions ??︎

It's pretty common to see Redux applications dispatching multiple actions while making AJAX requests, such as REQUEST_START, REQUEST_SUCCEEDED, and REQUEST_FAILED. This relates to the React world's emphasis on describing as much as possible in terms of explicitly tracked state (which carries over to Redux).

If you want to show some kind of "loading..." spinner, a React app shouldn't just go call $("#loadingSpinner").toggle(). Instead, it should update some kind of state, and use that to determine that it needs to show the spinner. Similarly, with Redux, you're definitely not required to dispatch actions like this when you make AJAX requests, but having those actions and the corresponding state values can be useful for updating the UI or other aspects of the application.

Normalized Data ??︎

I covered the main reasons and benefits of normalization in the Structuring Reducers - Normalizing State Shape section of the Redux docs. I'll paste the most relevant section here.

A normalized state shape is an improvement over a nested state structure in several ways:

  • Because each item is only defined in one place, we don't have to try to make changes in multiple places if that item is updated.
  • The reducer logic doesn't have to deal with deep levels of nesting, so it will probably be much simpler.
  • The logic for retrieving or updating a given item is now fairly simple and consistent. Given an item's type and its ID, we can directly look it up in a couple simple steps, without having to dig through other objects to find it.
  • Since each data type is separated, an update like changing the text of a comment would only require new copies of the "comments > byId > comment" portion of the tree. This will generally mean fewer portions of the UI that need to update because their data has changed. In contrast, updating a comment in the original nested shape would have required updating the comment object, the parent post object, the array of all post objects, and likely have caused all of the Post components and Comment components in the UI to re-render themselves.

Note that a normalized state structure generally implies that more components are connected and each component is responsible for looking up its own data, as opposed to a few connected components looking up large amounts of data and passing all that data downwards. As it turns out, having connected parent components simply pass item IDs to connected children is a good pattern for optimizing UI performance in a React Redux application, so keeping state normalized plays a key role in improving performance. (I talked more about how normalization matters for performance in my post Practical Redux, Part 6: Connected Lists, Forms, and Performance.)

Selector Functions ??︎

It's perfectly legal to write code that accesses nested portions of the state tree directly, such as const item = state.a.b.c.d. However, the standard software engineering principles of abstraction and encapsulation once again come into play. If looking up certain fields can be complicated, than it's probably a good idea to encapsulate that work in a function. If other parts of the application shouldn't be concerned with the exact structure of the state, or exactly where to find a particular piece of data, it's probably a good idea to encapsulate that lookup process in a function. "Selector functions" are thus simply functions that read in some portion of the state tree and return some subset or derived data.

The use of selector functions with Redux was originally inspired from the idea of "getters" in NuclearJS, which allowed you to subscribe to changes in certain keys of state, and derive data. A similar approach was proposed during Redux's development, and the concept was turned into the Reselect library.

Selector functions can simply be plain functions, but Reselect generates selector functions that can easily use multiple other selector functions as inputs, and memoize them so that the output selector only runs when the inputs change. This is an important factor for performance in two ways. First, expensive filtering or other similar operations in the output selector won't re-run unless needed. Second, because the memoized selector will return the previous result object, it will work nicely with the shallow equality/reference checks in connect (and possibly PureComponent or shouldComponentUpdate).

React-Redux: connect, mapState, and mapDispatch ??︎

It's possible to import the store directly into every component file, write the code to have the component subscribe to the store, extract the data it needs whenever the store is updated, and trigger a re-render of the component. None of that process is "magic". But, repeating one of the themes of this post, it's good software engineering to encapsulate the process so that Redux users don't have to deal with writing that repetitive logic themselves.

The React-Redux connect function serves several purposes:

  • It automatically handles the process of subscribing to the store itself
  • It implements the logic for extracting the pieces of state the wrapped component needs
  • It ensures the wrapped component only re-renders when needed
  • It shields the wrapped component from ever actually needing to know that the store exists or that its props are actually coming from Redux

A mapState function is basically a specialized selector function, which always receives the entire state as one argument, may receive the wrapped component's own props as a second argument, and must always return an object. The returned object's contents are, per the name, turned into props for the wrapped component.

A mapDispatch function allows injection of the store's dispatch method, enabling the creation of logic and functions that dispatch actions from the component. If no mapDispatch function is provided, the default behavior is to inject dispatch itself as a prop so the component itself can dispatch actions. Otherwise, the object returned by mapDispatch is also turned into props. Since the most common use case is to wrap up action creators so their returned actions are passed straight to dispatch, connect allows an "object shorthand" syntax - an object full of action creators can be passed instead of an actual mapDispatch function.

If you think about it, connect and the wrapper components it generates act like a lightweight Dependency Injection mechanism (especially via use of React's context mechanism for making the store reference available to nested components). This enables easier testing, since the component isn't reliant on a specific store instance, but also opens up potential advanced use cases like using separate stores in different parts of the component tree, or even overriding/wrapping the store functions that are exposed to nested components.

Summing Up Common Patterns ??︎

Overall, these common patterns and approaches generally can be seen as the result of either core Redux design decisions, or straightforward software engineering principles like encapsulation, de-duplication, and reusability. Other than plain action objects, all of these concepts are indeed optional, and if you don't want to use them you don't have to, but there are good reasons why they exist.

Philosophy and Variations in Usage ??︎

I've looked at a lot of different Redux-related code. I've read through hundreds of libraries, applications, tutorials, and articles, and I've seen a huge variety of styles, approaches, and implementations. Between that and my status as a Redux maintainer, I think it's fair to say that I'm an expert on how Redux is used. It also means that, as I said in the intro, I Have Opinions about what "good Redux code" looks like, and what qualifies as "idiomatic Redux "code". We've now reached the part where I'm going to express those opinions. (You have been warned :) )

So, in this last section, we'll look at several variations in how Redux can be used, and I'll offer my thoughts on whether these things are or are not in keeping with the spirit of Redux.

Independent Slice Reducers vs All-Updates-Together ??︎

As discussed earlier, the primary intended reducer structure is slice reducers composed together. However, since reducers are just functions, there's an infinite variety of ways to write and structure reducer logic. I discussed some alternate approaches in the Structuring Reducers - Beyond combineReducers docs section, including some examples of sharing data between slice reducers, sequencing dependencies, and use of higher-order reducers to wrap existing reducers with additional functionality.

One complaint I've seen is that having many separate slice reducers responding to the same action makes it harder to figure out what actually gets updated for that action. An example of this is the article Problems with Flux, written shortly after Redux was released. A couple quotes:

As a developer, I wish to have an overview of all the state updates an action triggers. In one place in my code. Line by line. Having a comprehensive summary somewhere in the code maximizes the possibility of getting the state update correct, especially if the order of the write operations is relevant!

I believe that Flux as of today splits the store/action relationship matrix on the wrong axis: You can easily see all the actions that affect one part of your state. But it's hard to figure out all the state one action affects. In my opinion, it should be the opposite.

I can see that point, and would say there's some truth to it. One the other hand, one of the reasons to have action constants is that it makes it really easy to "Find All Usages" in your codebase, so it doesn't seem like it's that much more work to see what's happening.

I would say that composed slice reducers still qualifies as more idiomatic, but if you'd rather structure your reducer logic oriented around the actions, that's fine, and another example of "do whatever works for you".

Action Semantics ??︎

This is a topic that has resulted in very long, very complicated, and very pedantic discussions, from people who care deeply about this sort of thing (and especially those who have experience with related concepts like Event Sourcing and CQRS). I actually don't have too many opinions in this area, but I'll try to highlight some of the areas of discussion.

Action Tense and "Setters" vs "Events" ??︎

There's been considerable debate over what verb tense to use when writing Redux action constants. Past tense, such as "LOADED_THING", can be seen as saying "here is something that occurred, how do you want to respond to it?". Present tense, such as "LOAD_THING", can be seen as more of a command - "go do this". This apparently ties into differences between Event Sourcing and CQRS, which I am only vaguely familiar with.

As a related example, take the idea of buying a pizza combo that gives you a pizza and a bottle of soda. Is it better to dispatch "PIZZA_BOUGHT", or is it better to dispatch "PIZZA_INCREMENT" and "SODA_INCREMENT" ?

In a Twitter thread about issues with thunks, Dan Abramov said:

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.

I see Dan's point, and he's not wrong, but there's a whole lot of gray area in here.

On the one hand, you can dispatch an action with whatever contents you want, and if no part of the reducer logic cares about it, it will sail through the system and no state will be updated. On the other hand, there usually is an implicit contract between the code that formats/dispatches the action, and some portion of the reducer logic that is specifically interested in that action. If the action isn't formatted correctly, then the reducer that's looking for that specific action type will either ignore it, or try to access a non-existent field and break.

In that sense, Redux is just like any other event-based / pub-sub style system. Triggering events or dispatching actions acts like a "function call at a distance", and if you don't provide the right parameters to whatever code is on the other end, it won't work.

Following on from that, there usually is an expectation that a certain chunk of the reducer logic is interested in this specific action. For example, if you have listA, listB, and listC in your state, and each of those is managed by copies of the same slice reducer function, dispatching "LIST_ITEM_INSERT" needs some kind of additional info to differentiate which list it's supposed to apply to, whether it be dispatching type "b/LIST_ITEM_INSERT" instead, or adding listId : "b" to the action. We frequently toss out the idea of "treating the store like it's a client-side database", and certainly an SQL update query would normally give specifics on what rows should be updated.

Ultimately, I don't think there is an absolute "right" answer here. I think this is a topic where it's really easy to get caught up in bikeshedding, and I'd rather spend that time building something useful :)

Multiple Dispatches ??︎

This topic carries on from the previous one.

In the couple applications I've worked on, I've found myself putting together a number of generic "primitive actions" that are usually dispatched as part of a larger sequence. In Practical Redux, Part 8: Form Draft Data Management, the logic for "stop editing a Pilot and save the changes" involves dispatching three actions: a specific "PILOT_EDIT_STOP" action that resets a flag, a generic "apply changes from draft to current for this item type+ID" action, and a generic "delete this item type+ID from draft" action. I certainly could handle all three steps in a single action, but as I was putting together the logic it made more sense to build the primitives first and then compose them together, especially because I needed to repeat the "draft data" behavior multiple times for any kind of item.

I addressed some of the concerns regarding multiple dispatches in my post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability. Quoting myself:

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").

Reducer Variations ??︎

Cursors and "All-In-One" Reducers ??︎

A "cursor" is (roughly) a function that offers read/write access to a particular nested piece of data. For example, if I had a notional cursor library and created a cursor like: const nestedCursor = cursor(state).refine(["a", "b", "c"]), the returned cursor object would let me manipulate state.a.b.c without having to worry about the intermediate layers myself.

A number of people have tried to apply that approach to updating Redux state, either directly through an actual cursor library, or via reducers that allow you to specify a state keypath and new value in the action. I've also seen people try to write "simplified reducers", where the only action in the entire application is along the lines of "SET_DATA", and the only reducer function is simply return {...state, ...action.payload}.

There was an extended discussion on how Redux relates to cursors in redux#155. Dan had some great comments in that thread, and actually anticipated people trying these approaches. I'll quote some of his comments here.

First, he describes why Redux avoids write cursors:

You can implement read-only cursors on top of Redux very easily. Just listen to the root changes, select a specific path and compare if the reference has changed since the last time. What Redux does not give you is write cursors. This is a core design decision made for a reason.

Redux lets you manage your state using composition. Data never lives without a reducer ("store" in current docs) that manages that data. This way, if the data is wrong, it is always traceable who changed it. It is also always possible to trace which action changed the data.

With write cursors, you have no such guarantees. Many parts of code may reference the same path via cursor and update it if they want to.

Later on, he addressed the idea of "SET_DATA"-style reducers:

Do you see that as a philosophical/pattern stance or as a technical one? In other words, is Redux somehow preventing write cursors or otherwise causing the developer to fall into the pit of success?

For example, if I created a single action set that took a path as a parameter and passed that around to my components, would I have implemented the same thing as a write cursor? Or would that somehow still be fundamentally different than a cursor?

That's a great question! You can totally do that.

What I'm saying is that cursors are very low-level API. Just like you can have a single React component for your whole application, you can have a single SET action and a single reducer that behaves akin to a cursor. In my experience it's not very practical but you can definitely do this.

One important difference is that your SET action will still flow through Redux's dispatcher which potentially allows us to implement things like time travel (#113) and logging/other middleware (#63) that stands between your action and the actual data. With a vanilla cursor approach, a framework just doesn't have the power to do something like that.

Both of these are things that are certainly possible with Redux, but do go against the intended spirit of "semantically meaningful actions that can be traced through the system".

Thick and Thin Reducers ??︎

As discussed in the Redux FAQ entry on "where do I put business logic?", there's valid tradeoffs with putting more logic in action creators vs putting more logic in reducers. One good point that I saw recently is that if you have more logic in reducers, that means more things that can be re-run if you are time-travel debugging (which would generally be a good thing).

I personally tend to put logic in both places at once. I write action creators that take time to determine if an action should be dispatched, and if so, what the contents should be. However, I also often write corresponding reducers that look at the contents of the action and perform some complex state updates in response.

I generally try to minimize the number of places I do return {...state, ...action.payload}. That approach is definitely helpful if I'm doing something like updating multiple possible fields in a form and don't want to write separate updateName / updateAge / updateWhatever handlers for each field.

I would say neither is more "idiomatic" specifically and are perfectly valid choices, but there are some benefits of erring on the side of more logic in reducers.

Reducers In Actions ??︎

I've seen several cases where people put "reducers into their actions". The code that dispatches the action includes a reducer or state update function, and the root reducer simply calls return action.reducer(state). This seems like a bad idea for a couple reasons. It not only loses traceability in the same way as write cursors, but also will break time-travel debugging because the functions won't serialize properly.

OOP and FP Variations ??︎

As discussed throughout these two posts, the core values of Redux are things like:

  • dispatch plain action objects
  • actions and state should be serializable to enable time-travel and persistence
  • use pure functions and plain data
  • there's no real need for classes anywhere
  • reducers should be able to independently listen to the same actions and respond appropriately.

Functional programming can be a tough concept to adjust to for many programmers that only have experience with OOP. (I myself am still somewhere in the middle of that spectrum - I'm fine with basic use of FP, but high-level FP usage is still mostly over my head, and there's aspects of OOP that I still find more comfortable.)

As a result, some people have tried to build various OOP layers on top of Redux. This includes libraries like Radical, React-Redux-OOP, Tango, Conventional-Redux, and many others. These libraries tend to follow similar patterns - things like defining classes whose methods are action creators and reducers, domain models to insert into the Redux store, "state classes" that wrap around state values, or "dispatching functions" instead of plain action objects.

These libraries generally wrap up and hide aspects of Redux's behavior, mostly for the sake of doing things in an OOP fashion. A common theme is that these libraries only support a 1:1 relationship between dispatched actions and reducers, when many reducers responding to one action is a core intended use of Redux.

There's an excellent slideshow called Reactive, Component-Based UIs that lays out some principles for "Thinking in React", and I think these principles totally apply to Redux as well. Quoting slide 3:

That is, while there is value in the items on the right, we value the items on the left more."

  • Functional over OO
  • Stateless over Stateful
  • Clarity over Brevity

It's also worth noting that any framework or library has certain idioms and expectations for how code is written and structured. When you start writing code that goes against those idioms, there are fewer people who are going to be familiar with your approach, and it's less likely that it will pick up any traction. I wrote an HN comment a while back about why a particular OOP Redux wrapper library wasn't getting attention, and went into more detail on that topic.

Overall, these OOP wrapper patterns may work at the technical level, but they definitely go against the intent and spirit of Redux.

Final Thoughts ??︎

There's an apocryphal story about an experiment where scientists taught monkeys to not climb up a ladder by spraying water at them, and later on, older monkeys would slap newer monkeys who tried to climb the ladder. Searching suggests that story is fake, but we understand the moral involved - many times people are told to do things without understanding why, and it becomes "received wisdom" that is passed down the chain. Eventually someone complains about the situation thinking the behavior is pointless, and it's because they don't understand the original reasoning for doing things that way in the first place.

I'd say that a lot of people's complaints about Redux follow this sort of category. They've seen "action creators" and "immutability" and folders named containers and dozens of different side effect middlewares, complain about the apparent excess baggage involved in using Redux, and "why do I need any of this stuff in the first place?".

Well, turns out that if you look at the history of Redux and how it's intended to be used, and then apply some pretty straightforward software engineering principles to its use in larger applications, you wind up with the common patterns and practices that we now can identify as "idiomatic Redux usage": plain action objects and action creators, slice reducers and switch statements, async middleware and connected components, selectors and normalized state. (For a great example of how these pieces fit together in practice, check out the recent article by Mapbox on Redux for state management in large apps.)

As I've said throughout these posts, in the Redux FAQ, and in comments on Reddit and HN and elsewhere: ultimately, it's your application, and your codebase. If you don't like these patterns, you don't have to use them. Redux is simple and flexible enough that you can twist it in a myriad of other "unintended" directions. But, like Chesterton's fence, you should at least understand that these idiomatic usage patterns exist for good reasons!

I hope this journey through the history and usage of Redux has been informative, and helps you and your team go forth and build better applications.

Until next time, Happy Redux-ing!

Further Information ??︎


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

梦见床是什么意思 种生基是什么意思 臭虫长什么样 介入科主要看什么病 嗓子痛吃什么好
什么什么的天空 口加一笔变成什么字 尿潜血是什么原因 脑血栓是什么原因引起的 信天翁是什么鸟
婆家是什么意思 家慈是对什么人的称呼 涤纶是什么材料 胃有幽门螺旋杆菌是什么症状 活血化瘀吃什么药
抖s是什么意思 咖啡加牛奶叫什么 静置是什么意思 夜幕降临是什么意思 吃醋对身体有什么好处
晚上睡觉口干是什么原因hcv8jop6ns8r.cn 眉毛旁边长痘痘是什么原因hcv8jop6ns6r.cn 什么是子公司inbungee.com 托班是什么意思hcv7jop6ns1r.cn obsidian什么意思fenrenren.com
空气刘海适合什么脸型hcv9jop1ns4r.cn 玫瑰花茶和什么搭配好hcv9jop6ns5r.cn 脚气挂什么科室hcv8jop2ns8r.cn 日本牛郎是干什么的hcv8jop0ns7r.cn 经常喝柠檬水有什么好处和坏处hcv9jop3ns0r.cn
灰溜溜是什么意思hcv7jop4ns8r.cn 打哈哈是什么意思hcv9jop0ns5r.cn 妊娠什么意思hcv9jop5ns3r.cn 什么窃什么盗hcv9jop3ns5r.cn 吃什么卵泡长得快又好hcv9jop2ns4r.cn
au750是什么金属hcv8jop7ns3r.cn 男生属鸡和什么属相配hcv9jop6ns5r.cn 阴差阳错代表什么生肖hcv8jop3ns9r.cn 糖化血红蛋白偏高是什么意思hcv9jop2ns8r.cn 嗓子发炎吃什么消炎药hcv7jop5ns5r.cn
百度