I'm not a professional React dev, but you ask an interesting question, so I try to set out my understanding of React internals.
React gives you an ability to write any javascript code in your render function and because of this, it is hard for React to notice what exactly changed in your DOM tree. That is maybe the main reason to develop the virtual DOM because it helps React to compare changes in memory and after this update DOM attributes that changed. Of course, there is plenty of options, that you can use to mitigate this specialty of React:
- You can avoid unnecessary re-renders if you carefully organize the state of your application and store it closer to its usage (it's obvious that if you store all your application state in the root component than any update of the state will cause the re-render of whole application)
- You can avoid unnecessary re-renders with the help of
shouldComponentUpdate
/React.PureComponent
. But be aware, that these optimizations doesn't comes for free.
- For rendering large lists you can use specialized libraries like react-vritualized. They speed-up interaction with large lists with a bunch of techniques like rendering only visible part of the list and carefully updating DOM elements.
Let's take a look at the simple example of how React
updates DOM tree:
class View extends React.Component {
render() {
return <div style={{padding: "10px"}}>
<div>{this.props.value}</div>
</div>
}
}
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 2,
b: 3
};
}
render() {
const pad = {padding: "10px"};
return (
<div style={{width: "250px"}}>
<div style={pad}><View value={this.state.a + this.state.b}/></div>
<div style={pad}><View value={`A: ${this.state.a}`}/></div>
<div style={pad}><View value={`B: ${this.state.b}`}/></div>
<button onClick={() =>
this.setState({a: this.state.a + 1, b: this.state.b - 1})
}>
Increase A / Decrease B
</button>
</div>
);
}
}
This example contains three labels and one button, that changes some values. You can notice, that the first label always renders constant value (5 in this example) that preserved across button clicks.
If you inspect re-renders with the help of React DevTools
(there is an extension for Chrome; when you install it you need to go in the Chrome DevTools
, choose Components
tab and enable option Highlight updates when component render
), you will see the following picture:
The reason because React re-render first label is because by default React.Component
works in such a way, that it re-renders component when reference to the props
parameter changed from previous render call. Notice that JSX
syntax expands in the sequence of function calls like this (where second arguments are the props
object):
<View value={this.state.a + this.state.b}/>
// became
React.createElement(View, { value: this.state.a + this.state.b })
[take a look at the articles about React internals without JSX
and other stuff]
You can see that reference to props
always updated and because of that React always needs to re-render element. But how can we avoid such a waste of time? We can fix this issue quite easily with the help of shouldComponentUpdate
method (you can find an in-depth explanation of this method in official doc). In simple words, this method always called before every re-render and if it returns false
than components stay unchanged. So, we can improve our View
component in the following way:
class View extends React.Component {
shouldComponentUpdate(nextProps, nextState, nextContext) {
return nextProps.value !== this.props.value;
}
...
}
And then only two labels re-renders after button clicks:
There is already an implemented class React.PureComponent
that does exactly what we needed - it shallowly compares previous props with next props and prevents re-render in case of objects equality (but note, that PureComponent
doesn't compare deep objects recursively).
In case of rendering large lists, you can avoid re-rendering of single elements with this trick, but there is still an issue with memory consumption for virtual DOM that always will be re-created after you top-level component render function call. Maybe you can avoid this with the help of some caching or storing components in the state directly, but I think it is simpler to use special libraries for this task.
To summarize, React is a great library, but it gives developers too much freedom to the developer, and because of this React can't optimize some things for you. There is a cool talk from Evan You (creator of Vue.js) that discuss the tradeoff between frameworks that restrict developer abilities and libraries, that gives the developers a full power of javascript.