I took a TodoList example to reflect my problem but obviously my real-world code is more complex.
I have some pseudo-code like this.
var Todo = React.createClass({
mixins: [PureRenderMixin],
............
}
var TodosContainer = React.createClass({
mixins: [PureRenderMixin],
renderTodo: function(todo) {
return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>;
},
render: function() {
var todos = this.props.todos.map(this.renderTodo)
return (
<ReactCSSTransitionGroup transitionName="transition-todo">
{todos}
</ReactCSSTransitionGroup>,
);
}
});
All my data is immutable, and PureRenderMixin is used appropriately and everything works fine. When a Todo data is modified, only the parent and the edited todo is re-rendered.
The problem is that at some point my list grows big as the user is scrolling. And when a single Todo is updated, it takes more and more time to render the parent, call shouldComponentUpdate
on all the todos, and then render the single todo.
As you can see, the Todo component has other component than the Todo data. This is data that is required for render by all the todos and is shared (for example we could imagine there's a "displayMode" for the todos). Having many properties makes the shouldComponentUpdate
perform a little bit slower.
Also, using ReactCSSTransitionGroup
seems to slow down a little too, as ReactCSSTransitionGroup
have to render itself and ReactCSSTransitionGroupChild
even before the shouldComponentUpdate
of todos is called. React.addons.Perf
shows that ReactCSSTransitionGroup > ReactCSSTransitionGroupChild
rendering is time wasted for each item of the list.
So, as far as I know, I use PureRenderMixin
but with a larger list this may be not enough. I still get not so bad performances, but would like to know if there are easy ways to optimize my renderings.
Any idea?
Edit:
So far, my big list is paginated, so instead of having a big list of items, I now split this big list in a list of pages. This permits to have better performances as each page can now implement shouldComponentUpdate
. Now when an item changes in a page, React only has to call the main render function that iterates on the page, and only call the render function from a single page, which make a lot less iteration work.
However, my render performance is still linear to the page number (O(n)) I have. So if I have thousands of pages it's still the same issue :) In my usecase it's unlikely to happen but I'm still interested in a better solution.
I am pretty sure it is possible to achieve O(log(n)) rendering performance where n is the number of items (or pages), by splitting a large list into a tree (like a persistent data structure), and where each node has the power to short-circuit the computation with shouldComponentUpdate
Yes I'm thinking of something akin to persistent data structures like Vector in Scala or Clojure:
However I'm concerned about React because as far as I know it may have to create intermediate dom nodes when rendering the internal nodes of the tree. This may be a problem according to the usecase (and may be solved in future versions of React)
Also as we are using Javascript I wonder if Immutable-JS support this, and make the "internal nodes" accessible. See: https://github.com/facebook/immutable-js/issues/541
Edit: useful link with my experiments: Can a React-Redux app really scale as well as, say Backbone? Even with reselect. On mobile
Here is a POC implementation I've done with ImmutableJS internal structure. This is not a public API so it is not ready for production and does not currently handle corner cases but it works.
The client code looks like
Here is a JsFiddle that logs the number of
shouldComponentUpdate
calls after a single element of the list (size N) is updated: this does not require to call N timesshouldComponentUpdate
Further implementation details are shared in this ImmutableJs github issue