[Work in progress] Add reselect and reduce unnecessary renders#2784
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2784 +/- ##
==========================================
+ Coverage 58.74% 59.01% +0.27%
==========================================
Files 59 59
Lines 2288 2301 +13
Branches 469 470 +1
==========================================
+ Hits 1344 1358 +14
+ Misses 944 943 -1
Continue to review full report at Codecov.
|
|
Thanks @asolove! I may give it a try tomorrow. Florent |
|
BTW, I see you use babel. Doesn't it introduce performance issues? (especially for Florent |
jasonLaster
left a comment
There was a problem hiding this comment.
I like where this is going. I'll review more closely soon
| if (process.env.NODE_ENV !== "production") { | ||
| const Perf = require("react-addons-perf"); | ||
| window.Perf = Perf; | ||
| } |
| export function getPause(state: OuterState) { | ||
| return state.pause.get("pause"); | ||
| } | ||
| const getPauseWrapper = state => state.pause; |
| // (right now) to type those wrapped functions. | ||
| type OuterState = { sources: Record<SourcesState> }; | ||
|
|
||
| const getSourcesWrapper = state => state.sources; |
There was a problem hiding this comment.
perhaps sourcesState or is that too opaque?
| } | ||
| const _getBreakpoints = createSelector( | ||
| getBreakpoints, | ||
| state => state, |
There was a problem hiding this comment.
does this negate the selector?
| return state.sources.sources.find(source => source.get("url") === url); | ||
| } | ||
|
|
||
| export function getSourceInSources(sources: I.Map<string, Source>, id: string) { |
There was a problem hiding this comment.
Well the problem is we already have a selector getSourceById that takes the full state and an id. This function takes just the actual sources map and an id. For various kinds of callers, we need both.
There was a problem hiding this comment.
Yeah - the underscore was meh when it was a private function, but below it's public... so i don't love it
There was a problem hiding this comment.
Yeah, not sure what to do without finding a better name for either the top-level "sources state" or the nested "sources by id map".
| @@ -284,8 +284,12 @@ export function getSourceByURL(state: OuterState, url: string) { | |||
| return state.sources.sources.find(source => source.get("url") === url); | |||
There was a problem hiding this comment.
we should have a separate URL to ID mapping
…eed to pass props.
* types * utils
|
@asolove I heard you also recently made a patch that is not landed yet in Firefox Dev Edition and fixed a big performance issue. And at work, I could notice a really good improvement of the performance already in master using the repository. Thanks a lot for this big effort! :) Florent |
|
@fflorent babel certainly adds a slow down, but it is worth it to have some consistency across runtimes. We don't use it to add experimental language features. |
Memoizes selectors to prevent unnecessary renders.
Associated Issue: #2727
Background
In issue #2727, @fflorent reported a very severe performance problem on pages with lots of script tags. In the course of debugging that problem, we noticed that all the components connected to the redux store were being re-rendered on every state change, even if the state change didn't effect the data that component read. That was odd, because Redux promises not to re-render
connected components if the props don't change (as tested by shallow equality). The culprit was our selector functions, which often usemapandfilterto build up results. Even if the underlying data stayed the same, these selectors return new objects and arrays every time they are called. A common solution to this problem is to use memoized selectors, which promise to return the same objects if the underlying data hasn't changed. One popular library for doing this is reselect.So the goal of this PR is to make components only re-render when their underlying data has changed, by memoizing selectors as necessary.
Summary of Changes
For each component, make it a
PureComponentand ensure its selectors don't change if their underlying data doesn't change:Test Plan
All existing tests should pass (no functionality should have changed).
To verify that re-renders have decreased:
Perf.start()Perf.stop()Perf.printExclusive()will now print a table with how long it spend re-rendering each component. This allows you to see if one component is changing because of unrelated data.