We're considering React for a DOM-heavy project and want to figure out the performance characteristics of the virtual-DOM rendering approach.
One thing that concerns me is that the virtual DOM is recomputed in its entirety on every minor state change. I can see the benefits of this model, but in an application with lots of elements and frequent minor updates this results in a lot of overhead for something as simple as a 'hover' effect.
This for example renders a sequence of N
divs and changes the CSS class onMouseOver
. On my system it's getting pretty sluggish from around N=5000
(http://jsfiddle.net/estolua/aopvp7zp/).
var D = React.DOM;
function generateItems(N) {
return _.map(
_.range(0, N), function (i) { return { id: '_' + i, content: '' + i }; }
);
}
function toggle(x, y) { return (y && x!==y) ? y : null; }
var Item = React.createClass({
render: function () {
var item = this.props.item,
appS = this.props.appState,
focF = this.props.focF;
return D.div({
className: 'item' + (appS.focused === item.id ? ' focused' : ''),
onMouseOver: focF
},
item.content
);
}
});
var App = React.createClass({
getInitialState: function () { return {N: 10, focused: null}; },
changeN: function(e) { this.setState({N: e.target.value}); },
focus: function (id, e) {
this.setState({focused: toggle(this.state.focused, id)});
},
render: function () {
var that = this;
return D.div(null, [
D.div(null, [
D.span(null, 'N: '),
D.input({value: this.state.N, onChange: this.changeN})
]),
D.div(null,
_.map(
generateItems(this.state.N),
function (i) { return React.createElement(Item, {
key: i.id, item: i, appState: that.state, focF: that.focus.bind(null, i.id)
});}
)
)
]);
}
});
React.render(React.createElement(App), document.body);
Is there a way to make such minor updates more efficient without giving up on the nice declarative form or is React just not suitable on this scale?
There are a couple potential solutions that allow you to continue to use the nice declarative model:
shouldComponentUpdate
When you have a lot of elements, shouldComponentUpdate
can be an easy win. The example from your comment isn't quite right; instead, you want to see if any of the this.props
values you care about are different from any of the next_props
you care about (and the same for this.state
and next_state
. For example, to only re-render when the focus prop changes, you might do:
shouldComponentUpdate: function (next_props, next_state) {
return this.props.appState.focused !== next_props.appState.focused;
},
(although this simple example doesn't handle the ID or handler changing).
While this is pretty manual, you can easily build abstractions or mixins that will compare objects (either shallowly or deeply, depending on the structure of your props) to see if they've changed at all:
var ShallowPropStateCompareMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
return !shallowEquals(nextProps, this.props) ||
!shallowEquals(nextState, this.state);
}
}
var MyComponent = React.createClass({
mixins: [ShallowPropStateCompareMixin],
// ...
});
In fact, this is already implemented as the PureRenderMixin. You can see this working in this example (note that other things can cause sluggishness with lots of DOM elements, including extensions and inline styles that affect the box model).
Another technique you can use is to localize the state changes; in your example, the top-level application state changes any time each item is hovered or unhovered; instead, you could delegate this behavior to the items themselves (or some other container for the item). That way, only the individual container items will have state changes, and most of the items will not be re-rendered at all.
Another idea I haven't tried is to group x out of the n items together in a grouping component (say, n=5000 and x=100); then, only the grouping component that contains the item that was changed would need to update. You could use shouldComponentUpdate on the grouping component so that the others do not need to be iterated over.
On my system 5000 is still ok, but do you really need to render > 1000 dom nodes in a real world scenario? If you care that much about performance maybe you could consider Mithril. It too uses a virtual dom approach but is really small and fast.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With