Learn about react's virtual dom and use this knowledge to speed up your application. In this comprehensive primer on the insides of the framework, we'll demystify JSX, let you show how to react, explain how to find bottlenecks, and share some tips for avoiding common mistakes.
One of the reasons React has been rocking the front-end world and shows no sign of going down is its approachable learning curve: After you wrap your head around, the learning curve. n.JSX also has the whole " state vs. props " concept and you can go.
If you're already familiar with the way React works, you can skip straight to "Fixing things" .
But to really master your responses, you need to think about your responses . This article is to help you solve this problem. Take a look at the responses made to one of our projects :
A huge response sheet for ebay business .
With hundreds of dynamic, multi-layered rows, understanding the finer points of the framework is critical to keeping the user experience running smoothly.
You definitely feel it when it happens. Input fields will get laggy, checkboxes will be checked first, and modal verbs will show up when difficult.
In order to solve these problems, we need to cover the entire journey, a react component from definition to you define (and then update) on the page. fasten your seatbelt!
behind JSX
The front-end circle is known in the process as "transpiling", even though "compiling" would be a more correct term.
React developers urge you to use a combination of html and javascript called JSX when writing your components. However, browsers have no clue about JSX and its syntax. Browsers only understand simple javascript, so JSX has to be converted into it. Here is one div
which has a class and some content:
<div className='cn'>
Content!
</div>
The same code in "official" javascript is just a function call with several parameters:
React.createElement(
'div',
{ className: 'cn' },
'Content!'
);
Let's take a closer look at these arguments. The second is an element type . For html tags it will be a string with the tagname. The second parameter is an object that contains all the element properties . It can also be an empty object if there is no empty object. All arguments below are children of the element . The text in the element is also counted as child elements, so the string "content!" is placed as the third parameter of the function call.
You can already imagine what happens when we have more children:
<div className='cn'>
Content 1!
<br />
Content 2!
</div>
React.createElement(
'div',
{ className: 'cn' },
'Content 1!', // 1st child
React.createElement('br'), // 2nd child
'Content 2!' // 3rd child
)
Our function now has five parameters: the element type, the attribute object, and three child elements. Since one of our children is also a well-known reaction, it will be depicted as a function call.
Now, we've covered two types of children: plain String
or another phone React.createElement
. However, other values can also be used as arguments:
- primitives
false
,null
,undefined
andtrue
- array
- React components
Arrays are used to group and pass sub-elements as a parameter:
React.createElement(
'div',
{ className: 'cn' },
['Content 1!', React.createElement('br'), 'Content 2!']
)
Of course, the power of react comes from the tags described in the html spec, but from user-created components such as:
function Table({ rows }) {
return (
<table>
{rows.map(row => (
<tr key={row.id}>
<td>{row.title}</td>
</tr>
))}
</table>
);
}
Components allow us to break templates into reusable blocks. In one example, the "function" component above accepts an array of objects with array row data, and returns a single React.createElement
call <table>
element and its row as children.
Whenever we place a component into a layout like this:
<Table rows={rows} />
From a browser perspective, we wrote this article:
React.createElement(Table, { rows: rows });
Note that this time our first parameter is not String
describing an html element, but a reference to the function we defined when we coded our component. Our properties are now ours props
.
Put the component on the page
So, we've converted all our JSX components to plain javascript, and now we have a series of function calls, among them other function calls, and other function calls... How do we convert them into dom elements that make up a web page?
For this we have a ReactDOM
library and its render
methods:
function Table({ rows }) { /* ... */ } // defining a component
// rendering a component
ReactDOM.render(
React.createElement(Table, { rows: rows }), // "creating" a component
document.getElementById('#root') // inserting it on a page
);
When it ReactDOM.render
is called React.createElement
last called, it returns the following objects:
// There are more fields, but these are most important to us
{
type: Table,
props: {
rows: rows
},
// ...
}
These objects make up the meaning of the virtual dom in reaction.
They will be compared to each other in all further renderings and finally converted to real dom (vs virtual ).
Here's another example: this time using div
a class attribute and several sub-attributes:
React.createElement(
'div',
{ className: 'cn' },
'Content 1!',
'Content 2!',
);
become:
{
type: 'div',
props: {
className: 'cn',
children: [
'Content 1!',
'Content 2!'
]
}
}
Note that in the past the individual parameter functions were used to find their in-position keys React.createElement
under a/s . So it doesn't matter if the children are passed as arrays or parameter lists - in the resulting virtual dom object, they will all end up together eventually.children
props
Also, we could add the child directly to the code and the result would still be the same:
<div className='cn' children={['Content 1!', 'Content 2!']} />
Once the dummy dom object is constructed, ReactDOM.render
an attempt will be made to convert it into a Dom node that our browser can display according to these rules:
-
If the
type
attribute holds the chord use the tag name - creates a tag that lists all the attributes listed belowprops
. -
If we have a function or class
type
- call it and recursively repeat a result. -
If anything goes
children
downprops
- repeat the process one by one and place the result inside the parent's Dom node.
So we get the following html (for our table example):
<table>
<tr>
<td>Title</td>
</tr>
...
</table>
rebuilt in
In practice, it render
is usually called once on the root element, and further updates are passed through state
.
Note: "re" is in the title! When we wanted to do, the real reaction started. Updating a page didn't replace everything. There is no way how we can do this. Let's start with the simplest one call again forReactDOM.render
the same node .
// Second call
ReactDOM.render(
React.createElement(Table, { rows: rows }),
document.getElementById('#root')
);
This time, the code above will be different from what we have already seen. The Response will create all Dom nodes from scratch and put them on the page, and the Response will start a reconciliation (or "diffing") algorithm to determine which parts of the node tree must be updated and can remain untouched.
So, how does it work? Just a few simple scenarios and understanding them our optimizations will help us a lot. Remember that we are now looking at objects that are representations of nodes in the response virtual dom.
- Scenario 1:
type
It is a string thattype
maintains the same distance on the phone andprops
does not change.
// before update
{ type: 'div', props: { className: 'cn' } }
// after update
{ type: 'div', props: { className: 'cn' } }
This is the simplest example: the dom remains the same.
- Assumption 2:
type
Still the same string,props
different.
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'div', props: { className: 'cnn' } }
As we type
still mean that the html element responds by knowing how to change its attributes via standard Dom calls without removing the node from a kind of tree.
- Scenario 3:
type
Has changed fromString
orString
to the component.
// before update:
{ type: 'div', props: { className: 'cn' } }
// after update:
{ type: 'span', props: { className: 'cn' } }
When the response now sees a different type, it won't even try to update our node: the old element will be removed ( downloaded ) along with all the children . So it can be very expensive to replace one element for an element of a completely different high-level dom tree. Fortunately, this rarely happens in the real world.
It's important to remember that react uses ===
(triple equals) comparison type
values, so they must be the same instance of the same class or the same function.
The next scene is more interesting because that's how we often use reactions.
- Scenario 4:
type
is a component.
// before update:
{ type: Table, props: { rows: rows } }
// after update:
{ type: Table, props: { rows: rows } }
"But nothing has changed!" you might say, and you would be wrong.
Note that the component's render
(only class components have this method explicitly defined), but with ReactDOM.render
. The word "rendering" is indeed overused in the reactive world.
If it type
's a reference to a function or class (i.e. a regex react component) and we start the tree adjustment process, then react always tries to look inside the component to make sure the value returned render
hasn't changed (a precaution against side effects). Flush and repeat every component down the tree - yes, complex rendering can get expensive too!
take care of children
In addition to the four common scenarios described above, we also need to consider the responsive behavior when an element has multiple children. Let's assume we have an element like this:
// ...
props: {
children: [
{ type: 'div' },
{ type: 'span' },
{ type: 'br' }
]
},
// ...
We want to shuffle these kids:
// ...
props: {
children: [
{ type: 'span' },
{ type: 'div' },
{ type: 'br' }
]
},
// ...
and then?
If "diffing", the react sees any internal arrays props.children
it starts comparing the elements in it with the elements in the arrays it saw before, and then looks at them in turn: index 0 will do with index 0, index 1, and index 1, etc. Compare. For each pair, the reaction will apply the set of rules described above. In our case it sees div
becoming a span
so Scenario 3 will be applied. This is not very efficient: imagine that we have deleted the first row from a table of 1000 rows. React will have to "update" the remaining 999 children as their content will now not be equal if compared to the previous rep index.
Luckily, react has built -ins to solve this problem. If the element has an key
attribute will compare the value of the element. key
Not by index. As long as the keys are unique, React will move around the elements without removing them from the Dom tree and putting them back (the process known in React is mount/unmount ).
// ...
props: {
children: [ // Now React will look on key, not index
{ type: 'div', key: 'div' },
{ type: 'span', key: 'span' },
{ type: 'br', key: 'bt' }
]
},
// ...
when the state changes
We didn't touch props
the part of reactive philosophy until now, but ignored it state
. Here's a simple "stateful" component:
class App extends Component {
state = { counter: 0 }
increment = () => this.setState({
counter: this.state.counter + 1,
})
render = () => (<button onClick={this.increment}>
{'Counter: ' + this.state.counter}
</button>)
}
So, we have a counter
key in our state object. When the button is clicked it increments its value and changes the button text. But what happens in a Dom when we do that? Which parts of it will be recalculated and updated?
The call this.setState
also causes a re-render, but not the entire page, but just a component itself and its children . Parents and siblings are free. This is handy when we have a large tree and we only want to redraw part of it.
pinning problem
We've prepared small demo apps so you can see the most common problems in the wild before we go to fix them. You can view its source code. here . You also need the react dev tools so make sure you have them installed for your browser.
The first thing we'll look at is which elements and when will cause the virtual dom to be updated. Navigate to the responsive panel in your browser's dev tools and select the "Highlight Updates" checkbox:
Respond with "Highlight Updates" checkbox in chrome
Now try adding a row to the table. As you can see, borders appear around every element on the page. This means that every time a row is added, the response is to compute and compare the entire virtual dom tree. Now try hitting a counter button in a row. You can see how the virtual dom is updated when it changes state
- only the relevant factor and its children are affected.
React to where the problem might be, but tell us the details: especially updating the problem means "diffing" elements or mounting/resetting them. To find out more information, we need to use react's built-in profiler (note that it won't work in production mode).
Add ?react_perf
any url for your app and go to the "performance" tab in chrome browser. Hit the record button and tap around the table. Add some lines, change some counters, and hit Stop.
React DevTools 'Performance' tab
In the resulting output, we are interested in "user timings". Zoom to the timeline until you see the Reaction Tree Coordination group and its children. These are the names of our components [latest] or [mountain] next to them.
Most of our performance issues fall into these two categories.
Either the component (and all branches from it) is being reinstalled on every update for some reason, we don't want it (slow reinstallation), or we're doing expensive reconciliations, large branches, despite not having any Change.
Fix things: install/install
Now, when we found out a bit about how react decided to update the virtual dom and learned how to see what's going on behind the scenes, we're finally ready to fix things! First, let's deal with mounts/unmounts.
If you just consider that multiple children of any element/component are represented as inside the array .
Consider this:
<div>
<Message />
<Table />
<Footer />
</div>
In our virtual dom it would be represented as:
// ...
props: {
children: [
{ type: Message },
{ type: Table },
{ type: Footer }
]
}
// ...
We have a simple Message
one that div
holds some text (think notices about your garden variety) and a huge Table
span of, let's say, 1000+ lines. They are both surrounded children div
so they are placed below props.children
the parent node and they don't happen to have a key. And react doesn't remind us to assign the key via console warning because the child element is being passed to the parent React.createElement
as a parameter list, not an array.
Now our user has dismissed the notification, Message
removed from the tree. Table
and Footer
the rest are.
// ...
props: {
children: [
{ type: Table },
{ type: Footer }
]
}
// ...
How does the reaction look? It sees it as an array of children that change shape: children[0]
hold Message
now it occupies Table
. There is no key to compare to, so compare type
since they both refer to the function (as well as the heterogeneous function) n.unmounts the whole Table
and then mounts it, rendering all the children: 1000+ lines!
So, you can add unique keys (but using keys is not the best option in this particular example), or go for a smarter trick: use short-circuiting boolean estimates which are a feature of javascript and many other modern languages. Look:
// Using a boolean trick
<div>
{isShown && <Message />}
<Table />
<Footer />
</div>
Even Message
out of the screen, the props.children
parent div
will still hold the 3 element, with children[0]
value false
(boolean). Remember true
/ false
, null
and are all allowed value properties undefined
of virtual dom objects ? type
We finally came to this conclusion:
// ...
props: {
children: [
false, // isShown && <Message /> evaluates to false
{ type: Table },
{ type: Footer }
]
}
// ...
So, Message
or not, our indices won't change, and of Table
course, will still reconcile with Table
(referring to the component's reference) type
starting anyway, but just comparing virtual doms is faster than deleting Dom nodes and creating them from scratch. .
Now let's look at something more evolved. We know you like ad hoc s. A higher-order component is a function that takes the component as a parameter, does something, and returns a different function:
function withName(SomeComponent) {
// Computing name, possibly expensive...
return function(props) {
return <SomeComponent {...props} name={name} />;
}
}
This is a very common pattern, but you need to handle it with care. consider:
class App extends React.Component() {
render() {
// Creates a new instance on each render
const ComponentWithName = withName(SomeComponent);
return <SomeComponentWithName />;
}
}
We are creating a special render
method inside a parent. When we re-render the tree, our virtual dom looks like this:
// On first render:
{
type: ComponentWithName,
props: {},
}
// On second render:
{
type: ComponentWithName, // Same name, but different instance
props: {},
}
Now, the response will like to just run an algorithm based on the above ComponentWithName
but, as this time, the same name refers to a different instance of the triple equality comparison fails, rather than a reconciliation, a full reinstallation must occur. Note that it also causes states to lose as described here . Fortunately, it's easy to fix: you need to always be on render
:
// Creates a new instance just once
const ComponentWithName = withName(Component);
class App extends React.Component() {
render() {
return <ComponentWithName />;
}
}
Fix things: update
So, now we make sure not to reinstall stuff unless necessary. However, any change to a component located near the root of the tree results in the reconnection and reconciliation of all subtrees. The structure is complex, expensive, and often avoidable.
Would be great to have a way to tell React not to look at a certain branch, as we are confident there were no changes in it.
This approach exists and it involves something called shouldComponentUpdate
it's component lifecycle . This method is called on every previous call to the component render
and receives new values for props and state. We are then free to compare them to the current value and decide if the component should be updated (returned). true
Or false
that is. If we return false
react doesn't re-render the component involved and doesn't look at its children.
Comparing the two groups is usually sufficient props
as well as state
a simple shallow comparison: if the values at the top level are different, we don't have to update. Shallow comparisons are not a feature of javascript, but there are a lot of utilities for that.
With their help, we can write our code as follows:
class TableRow extends React.Component {
// will return true if new props/state are different from old ones
shouldComponentUpdate(nextProps, nextState) {
const { props, state } = this;
return !shallowequal(props, nextProps)
&& !shallowequal(state, nextState);
}
render() { /* ... */ }
}
But you don't even have to code it yourself, because the response includes this feature, the class call React.PureComponent
. It's like React.Component
only a shallow prop/state comparison shouldComponentUpdate
has been implemented for you .
Sounds like a no-brainer, just swap Component
it for PureComponent
being extends
part of your class to define and enjoy efficiency. But don't be so fast! Consider these examples:
<Table
// map returns a new instance of array so shallow comparison will fail
rows={rows.map(/* ... */)}
// object literal is always "different" from predecessor
style={ { color: 'red' } }
// arrow function is a new unnamed thing in the scope, so there will always be a full diffing
onUpdate={() => { /* ... */ }}
/>
The code snippet above demonstrates the three most common code anti-patterns. Try to avoid them!
If you take care to create all objects, arrays and functions out of
render
definitions and make sure they don't change between calls - you are safe.
You can observe that PureComponent
in the update demo all table positions Row
s are "cleaned". If you turn on "Highlight Updates" in Responsive DevTools, you will notice that only the table itself and the new rows are being rendered in row inserts, all other rows remain unchanged.
However, if you can't wait to go all out on pure components and implement them everywhere in your application - stop yourself. Comparing the two groups props
and state
not being free is not worth it for most basic components: more time to run. shallowCompare
than in the algorithm.
Use this rule of thumb: pure components are fine for complex forms and tables, but they often simplify simple elements like buttons or icons.
Follow Xiaobian for more exciting content
You can also join our front-end learning qun: listen to quality free learning classes every day
At the same time, I will share the high-quality information for you, 2-1-3-1-2-6-4-8-6 Invitation code: Fallen Leaves