Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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:

http://hypirion.com/imgs/pvec/9-annotated.png

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
142 views
Welcome To Ask or Share your Answers For Others

1 Answer

In our product we also had issues related to the amount of code being rendered, and we started using observables (see this blog). This might partially solve your problem, as changing todo will no longer require the parent component that holds the list to be re-rendered (but adding still does).

It might also help you in re-rendering the list faster as your todoItem components could just return false when the props change on shouldComponentUpdate.

For further performance improvements when rendering the overview, I think your tree / paging idea is nice indeed. With observable arrays, each page could start to listen to array splices (using an ES7 polyfill or mobservable) in a certain range. That would introduce some administration, as these ranges might change over time, but should get you to O(log(n))

So you get something like:

var TodosContainer = React.createClass({
  componentDidMount() {
     this.props.todos.observe(function(change) {
         if (change.type === 'splice' && change.index >= this.props.startRange && change.index < this.props.endRange)
             this.forceUpdate();
     });
  },    

  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.slice(this.props.startRange, this.props.endRange).map(this.renderTodo)
     return (
          <ReactCSSTransitionGroup transitionName="transition-todo">
                 {todos}
          </ReactCSSTransitionGroup>,
     );
  }

});

The central problem with large lists and react seems that you cannot just shift new DOM nodes into the dom. Othwerwise you won't need the 'pages' at all to partition the data in smaller chunks and you could just splice one new Todo item into the dom, as done with JQuery in this jsFiddle. You could still do that with react if you use a ref for each todo item, but that would be working around the system I think as it might break the reconciliation system?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...