I'm just starting to learn React JS and am going through the tutorial on building the Tic Tac Toe game:
https://reactjs.org/tutorial/tutorial.html
I notice that if I put a breakpoint in the code where the Game component gets rendered, it gets called every time you click on a square in the game. At first I thought that this is bad performance but found out that React uses a virtual DOM and only updates the real DOM when it needs to. But that makes me wonder when it does this. Looking at the DOM elements when I change a value on a square, I have no idea whether React is updating the entire DOM table that the game uses or just a single cell in the table. Would seem rather senseless to rebuild the entire table just because the data in one cell gets updated. Is there a way to actually tell what parts in the real DOM get updated?
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:
shouldComponentUpdate
/React.PureComponent
. But be aware, that these optimizations doesn't comes for free.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.
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加