How to optimize React applications that use Redux
reactrocket.com
reactrocket.com
It basically tries to enforces a few old ideas, pure functions, message passing and immutability, with no help from the language, which results in verbose and ugly code.
`a.b = c` becomes `Object.assign({}, a, { b = c })`
and
`object.method(data)` becomes `store.dispatch({type: METHOD, data})` with the implementation code of the "object" being a huge "switch" statement.
If you want immutability, pure functions and message passing, then why not use a language that is build around them and make your life easier? Clojurescript maybe?
Also having a single store for all your data, definitely doesn't help with speed as a single event suddenly becomes an O(LogN) operation in the best case.
- It has helped many developers organize their "write logic" and state management, and made it easier to test that code
- Time travel debugging is a fantastic tool for understanding what happens in your application
- One of my favorite quotes: "Redux is a generic framework that provides a balance of just enough structure and just enough flexibility. As such, it provides a platform for developers to build customized state management for their use-cases, while being able to reuse things like the graphical debugger or middleware."
- And it doesn't hurt that Dan Abramov is a well-known figure in the React community, and an incredibly helpful and friendly person too.
No, Javascript doesn't have immutability built-in (sadly), but that doesn't mean you should totally give up on applying those principles in Javascript code. There's WAY more developers writing JS than Clojurescript, and those devs can still make use of tools like Redux in their applications. Besides, there's a variety of approaches for handling data immutably, whether it be `Object.assign`, the Stage 3 object spread operator, Immutable.js, or the many immutable update utility libraries out there.
Your comment about speed doesn't make sense. A few function calls and switch statements will execute quickly overall, and perf benchmarks will show that your reducer logic is almost never going to be the bottleneck in a Redux application.
Some links for further related reading:
- https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
- http://redux.js.org/docs/faq/Reducers.html#reducers-use-swit...
- http://redux.js.org/docs/faq/Performance.html#performance-al...
- https://github.com/markerikson/react-redux-links/blob/master...
- https://github.com/markerikson/redux-ecosystem-links/blob/ma...
As far as I understand it with redux you end up having all of your application's state in the store. Unless you split it?
> Using local component state is fine. As a developer, it is your job to determine what kinds of state make up your application, and where each piece of state should live. Find a balance that works for you, and go with it.
Some common rules of thumb for determining what kind of data should be put into Redux:
> - Do other parts of the application care about this data?
> - Do you need to be able to create further derived data based on this original data?
> - Is the same data being used to drive multiple components?
> - Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
> - Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
I've seen some people say that you should put literally every single value in your app into Redux. You certainly _can_ do that, but I would see it as overkill. I'm much more pragmatic about it - put it into Redux if it makes sense to, per the rules of thumb in the FAQ.
> > - Do other parts of the application care about this data? > - Do you need to be able to create further derived data based on this original data? > - Is the same data being used to drive multiple components? > - Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)? > - Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
Won't having half your state outside the store and the other half inside add unneeded complexity and make your code even more difficult to read?
But, as I said, it's totally up to you to decide what lives where :)
Of course, once you need to access a piece of state from outside that component you should probably move it to the store. But until then you are just creating unnecessary complexity by cluttering global state.
And it usually isn't anything as sane as half in the store and half in one other place.
It's like 70% in the store, and 5% in component#1, and 8% in component#2, and 16% in component#3 (duplicating some stuff from component#1 and sorta duplicating some stuff from component#2 with some notes that someone should refactor all this and move those to redux) and the rest of it is only god knows where.
And then there is some hard to track down UI bug because using setState() turns out to be really much slower than you think it is going to be.
But Redux is so boilerplatey and heavy handed that it made sense in each individual case to the original coder/reviewer to not use it.
Where immutability really comes into play with React itself is optimizations via `shouldComponentUpdate`. The fastest way to determine if a component needs to update is shallow equality comparisons of current and next props/state, which requires that you do immutable data updates to produce new object references. Two good articles on the topic are [0] and [1].
[0] http://reactkungfu.com/2015/08/pros-and-cons-of-using-immuta...
[1] https://www.bennadel.com/blog/2903-why-should-i-care-about-i...
Not necessarily - if something downstream is looking at the values (such as a shouldComponentUpdate check), the first mutation may cause the current value to === the next value and prevent a render. I know you called this out as an "optimization" but you don't always know what's happening inside your components, especially if 3rd party libraries are involved.
The primary reasons to actually update data immutably before calling `setState` are for easy and consistent implementation of `shouldComponentUpdate` / use of `PureComponent`, and general FP principles that immutability should be preferred over mutation.
That shouldnt be an afterthought for a state management library that uses the principles of event sourcing to create a state tree.
I think its these two ideas clashing that causes the verbose code, the community complaints, and slow code as described in the post. Why should i need to mutate references?
IMO it would be alot more palatable if redux persisted events, not the store. And was opined about the event structure, instead of everything else. ie. Use inheritance/events to then control UI updates.
ie. NOT Comment_Added { postId:.., comment: .. }
rather Comment_Added : PostDomainEvent {comment: ..}
now on the page where i am viewing a current_post, i can simply connect to my state, and re-render whenever i see a PostDomainEvent of id == current_post_Id. I can also buffer re-renders based on the amount of postDomainEvents i see per second.
This way, as someone using the library, my events are my focus, not my store. I dont need to remember to mutate this, or do that. I get precise control, and anyone can implement the store in any technology they want. sure i need an "unreduce" to get time-travel debugging, but thats not a trade-off thats forced on ppl who dont need it.
I'd very much disagree with the phrases "can't scale to large data-sets because of immutability", and "app can't properly model your domain". If you follow the suggested pattern of normalizing your state like a database [0], then modeling relationships between data types is entirely feasible, AND you can update the individual items efficiently (and if you don't want to write that code manually, there's a ton of addon libraries to help you manage those relationships [1] [2] [3]). At that point, the only real limit to how much data you load is browser memory, same as any other client-side framework.
[0] http://redux.js.org/docs/recipes/reducers/NormalizingStateSh...
[1] https://github.com/tommikaikkonen/redux-orm
[2] http://blog.isquaredsoftware.com/2016/10/practical-redux-par...
[3] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
Additionally, i question the logic of "normalising" my data structure for the client --> just so i can update my UI efficiently. Whats the point of using universal JS?
What i am saying is redux NEEDS immutability to deliver on its promise "out of the box" - lets face is, sure u can extend redux to mute this requirement, but then whats the point of all that boilerplate? So u can use devtools? So u can use off-the-shelf middleware to speed up ur dev? RAPID app development? Cause in a few years those tools wont be supported, and u will be stuck with a legacy boilerplate mess.
i argue that a FULLY event-sourced model would negative all this and make everything scale without any tricks. The diciple of event-sourcing will negate the need of immutability in every case. Additionally the cognitive load of "events, reducers and reference equality" is very different from "events, reducers and events".
Go all with event sourced domain models and get all the benifits. Represent the way your domain does. Represent ur state changes to your domain with events. Make an event heirarchy so u can preciesesly update ur UI around its state transitions.
If redux persisted events INSTEAD of the composed state, none of these problems would exist AND you would get time-travel. Then u can snap-shot ur state periodically to reduce load times.
Instead redux uses events to trigger state changes, and snapshot diff'ing to trigger ui changes.
I'm curious - why do you say O(LogN)?
To reconstruct your store state, which is basically a hard-coded tree structure.
It doesn't seem like there's any particular reason that a state reconstruction algorithm needs to be more expensive than O(1). What kind of advantages does this design have that an append store wouldn't give?
My blog post "The Tao of Redux, Part 1 - Implementation and Intent" goes into a lot of detail on the history, design, and influences behind Redux [0], and Dan's post "You Might Not Need Redux" [1] talks about the tradeoffs that Redux asks you to make and the kinds of benefits you get in return.
[0] http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao...
[1] https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
If the User marks TODO item #17 as complete (or deletes it), you have to either dig through the state array looking for the TODO with the matching ID, which is O(n), right? I'm sure there are times an array is more conventient. These trade offs are in the redux docs.
As you said, I covered this in the "Structuring Reducers - Normalizing State Shape" and "Immutable Update Patterns" sections of the docs: http://redux.js.org/docs/recipes/reducers/NormalizingStateSh... and http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePat... .
{
todos: {
1: {},
2: {},
...
}
}
And so all references are O(1). Real applications often have much bigger and more complicated structures, but they're nearly always of constant depth.The 0.01% is if your action has to do some super-complicated computations to get the next state.
So realistically, considering the construct in play, you can do tens of thousands of calls to the reducers to get a new state every second.
That's not gonna be the bottleneck.
Well Redux is a 'port' of Om from Clojurescript to native JS.
`a.b = c` becomes `Object.assign({}, a, { b = c })`
can be written as
`{...a, b: c}`
(this desugars in typescript to) `Object.assign({}, a, { b: c });` [1] (note also you had a minor typo there with equal sign {b=c} should be a colon, I feel like I make that mistake multiple times per day when writing typescript / javascript, having two different assignment operator characters in the language is not good imho. i'd prefer if it was all just `=`, that will never change at this point though. )
For your seconds example,
`store.dispatch({type: METHOD, data})`
perhaps the Proxy object[2] (which allows for delegation, method_missing like functionality) could be used to turn the calls back into store.method(data)
0: Beauty is in the eye of the beholder as they say.
1: https://www.typescriptlang.org/play/#src=const%20a%20%3D%20%...
2: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[1] https://babeljs.io/docs/plugins/transform-object-rest-spread...
In fact, sometimes I like to use TS because it allows me to skip the Babel part of the process entirely.
That's very true, but it still not expressively correct in my eyes.
If you have complex action dispatchers the asynchronous logic is still contained in a single function. This is easier to test than a set of loosely coupled data objects that are firing of async events and calling each other through multiple levels of indirection.
So all it takes is one mistake or one new developer or whatever and suddenly all your assumptions break.
If you violate that principle, then yes, your assumptions break. That's why you should keep it in mind. You can't really blame the framework if you purposely go against its basic rules.
[1] http://redux.js.org/docs/introduction/ThreePrinciples.html#c...
I just wish they had a reasonable way to _enforce_ those rules (though I understand it's not really possible) – or at least make people really go out of their way to break convention.
I don't use Redux, but the original Facebook engineering announcement videos helped me understand React and Flux tremendously: https://www.youtube.com/watch?v=nYkdrAPrdcw
If you're talking about Redux specifically -- I agree with you. I think it's ugly and confusing.
[0] http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao...
If anyone's interested, I discussed related aspects of good Redux performance in my blog post "Practical Redux, Part 6: Connected Lists, Forms, and Performance" [0], and have a lot of other articles on both React and Redux performance topics in my links list at [1]. Also, my post "Practical Redux, Part 7: Form Change Handling" [2] specifically shows off a React component I built to buffer change events from text inputs to help improve UI performance.
[0] http://blog.isquaredsoftware.com/2017/01/practical-redux-par...
[1] https://github.com/markerikson/react-redux-links/blob/master...
[2] http://blog.isquaredsoftware.com/2017/01/practical-redux-par...
Redux is too simple to suffer from performance problems.
Preethi Kasireddy did a fantastic presentation at ReactConf comparing Redux and MobX [0], Robin Wieruch wrote a good article comparing them last year [1], there was a Reddit thread with good discussion as well [2], and I have links to more comparisons at [3].
They're different tools taking very different approaches to solving the same kinds of problems, and they definitely appeal to different developers.
[0] https://youtu.be/76FRrbY18Bs
[1] http://www.robinwieruch.de/redux-mobx-confusion/
[2] https://www.reddit.com/r/reactjs/comments/4npzq5/confused_re...
[3] https://github.com/markerikson/react-redux-links/blob/master...
I've never tried MobX, but it looks pretty impressive too. I can imagine it working really well if you have the discipline to not leak your mutations all over the place.
MobX is useful mostly as the data model / selector part of redux. It does an awesome job of it. Once you've defined your core data model, you can create layers of computed properties and transformations out to your view. The view then rerenders in a really optimal way.
Where redux shines is the prescriptive nature. The layout of a redux app is fairly well defined at this stage and there are "right" and "wrong" places to put (and look for) different types of code.
With MobX there's still no clear way to layout your application, so, as you say, you need to be more disciplined with your approach.
Vanilla Redux that somewhat closely mimics the Elm architecture is fantastic at it, but average Redux apps in the wild are...mediocre at best.
You literally can't fuck up rendering performance, provided you don't have a single huge component the renders your entire state.
Redux falls apart pretty hard when people start layering sugar and abstractions on top. It's my pattern of choice, but for teams that insist on abstracting the Redux boilerplate away, I steer them to MobX.
That said, my subsequent apps have gone much better and I definitely prefer it to Redux, but I can see the appeal of Redux's more structured approach.
After having used Mobx for more than 10 months now, I still have to find areas that I dislike about Mobx.
A direct quote from https://github.com/mobxjs/mobx-state-tree
> mobx-state-tree is a state container that combines the simplicity and ease of mutable data with the traceability of immutable data and the reactiveness and performance of observable data.
Out of the box, you get absolutely perfect rendering update performance. You literally can't do it any better, thanks to observables.
https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
If so, what about it is slow? And slower than what?
An app that uses redux poorly becomes slow. Naive devs then claim redux makes their app slow. Propeelu using reselect solves a lot of the problems.