-
Notifications
You must be signed in to change notification settings - Fork 112
What are the performance implications of rendering the entire tree on location change? #182
Description
According to this line:
https://github.com/FormidableLabs/redux-little-router/blob/master/src/components/provider.js#L65
export const RouterProvider = connect(state => ({
router: state.router
}))(RouterProviderImpl);
export default ({ store }: ProvideRouterArgs) =>
(ComposedComponent: ReactClass<*>) => (props: Object) =>
<RouterProvider store={store}>
<ComposedComponent {...props} />
</RouterProvider>;
the <Provider />
component renders the entire component tree every time the location state changes.
I see how it's efficient in that all your <Fragment />
components have no listeners, and as they are re-rendered, they simply grab router state from context
:
render() {
const { children, forRoute, ...rest } = this.props;
const { router, parentRoute, parentId } = this.context;
const { store } = router;
const location = store.getState().router;
// ..
But overall this is highly inefficient. Your entire tree is rendering (even if just the virtual DOM) until a shouldComponentUpdate
returns false (if you even have one).
The way Redux works is it subscribes components at the component-level:
https://github.com/reactjs/react-redux/blob/4d302257e3b361731f44b1f546e547ed578c8eec/src/components/connectAdvanced.js#L218
Thereby giving you the opportunity to isolate updates to the smallest relevant leaf nodes/components.
Perhaps this is the only way, given there's no other way to know where all the <Fragment />
components will be. I see that's how React Router works as well. I use Redux directly, so I'm less familiar with path-aware route components.
Overall it seems that Redux's "static" subscriptions created and defined by connect
are a clear performance winner. Essentially you trade a bit more upfront work from the user to tell Redux which components need to [possibly] re-render, rather than re-render the whole tree. In addition, it's only "possibly," in which case only the comparisons are performed.
I mean, am I missing something, we're talking about renders of the ENTIRE tree vs. comparisons (which you have plenty of capability to optimize in userland) and possibly no render at all in plenty of cases.
I personally use route-aware actions to determine state, and let well-named state handle the rest. Basically I'm on the "switch" side of the "flexibility continuum" described here:
https://github.com/FormidableLabs/redux-little-router#fragment
I also struggle to see the value in nested routes. I always have with regards to React Router. From my perspective, it's added complexity having to carry around path segments throughout your components. It seems to be pretending to solve a problem--because it's really not that hard to toggle on a navbar or a sidebar for a given section of the site in response to state. Or return a component from a map whose key matches some state (or the token "switch").
And this whole example never happens in the wild:
/home/bio/dat-boi:
<Fragment forRoute='/home'>
<div>
<h1>Home</h1>
<Fragment forRoute='/bio'>
<div>
<h2>Bios</h2>
<Fragment forRoute='/dat-boi'>
<div>
<h3>Dat Boi</h3>
<p>Something something whaddup</p>
</div>
</Fragment>
</div>
</Fragment>
</div>
</Fragment>
There's no /home/bio/
URL that just shows the h2 "Bios"? Who's site actually does that? That URL is usually unreachable. It always comes paired with a user slug. I mean I understand, a better example would be where the 3 segments are: /home/posts/:slug
, and with the slug you show a post profile and without it, you show a posts list. But is this really such a complex problem it's worth all our components being aware of paths, not to mention all the additional nesting?
paths are also subject to change, whereas it's your job to use well-named variables (i.e. state keys) that will stick around for longer.
Unlike React Router, we have the benefit of state at our disposal. So we have another mechanism to control what is visible. It seems once you have state, you no longer need <Route />
or <Fragment />
components. Route components essentially seem to be a carry-over from a bygone era.
I've always felt that React Router and it's "everything is a component" concept is a solution looking for a problem. The problem just doesn't exist, at least not with global reactive state at your disposal. If it did, a better example than that would be in the readme right here.
That all said, what I love about Redux Little Router is how it bi-directionally dispatches route-aware actions as the result of address bar changes and changes the address bar as the result of actions. With path params being extracted and passed to your reducers, that's all the power you need. Connect the components you need to be aware of such state, and done.
You have to think, if we didn't have URLs to deal with, is littering our component tree with paths what we would do? It seems to add more complexity and costs far more in terms of performance.
Am I missing something? Is this just for people that are hooked to the React Router "everything is a component" interface, but want it predictably driven by Redux state? Are most people using these <Fragment />
components, or are they using their own state derived from route-aware actions? My guess is everyone's following the React Router interface--because that's just what we do in Reactlandia. Please enlighten me though. Am I'm missing something that everyone else sees?
The way redux-first-router does this by the way is simply allowing users to use react-redux's "sideways data-loading" strategy (which is extremely efficient) to make their own routing components using location
state in redux. RFR has no concept of <Route />
or <Fragment />
components. Instead it has better formatted action types
with the name of the route, i.e. instead of the equivalent of LOCATION_CHANGED
as the type
for every call to history.push
. Essentially making route-aware components comes down to using Redux state like normal. Most of the time in your connected components, instead of using the route type
as state, you use derived state in the form of the state contained in other reducers, since it's now extremely easy for reducers to listen and switch over route changes.