Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about optimizing reconciliation on lists where only one/two children change #3309

Closed
radubrehar opened this issue Mar 4, 2015 · 5 comments

Comments

@radubrehar
Copy link

I have a list with many children (>1000). When a change occurs in my scenario I have to setState on the list, which re-renders the list and all its children. I have optimized the children - they are keyed properly and also have shouldComponentUpdate implemented to be very very quick. When the change in the list occurs, I only have to update 1 or 2 children (I know exactly which ones), even if I may have hundreds/thousands of children. Yet it still takes a noticeable amount of time, since React reconciliation loops over all the children and checks if they should be updated or not.

Is there any special approach I can take that I may be unaware of?

@jeffchan
Copy link
Contributor

jeffchan commented Mar 4, 2015

I've run into this problem as well. I found that most of the time was spent in constructing the element descriptors in the vDOM for diffing. So the solution is to cache the descriptors and manage that cache. It's ugly, but it works. I believe React 0.14 will be much better at this because you can inline elements #3228

@jimfb
Copy link
Contributor

jimfb commented Mar 4, 2015

If @radubrehar is using shouldComponentUpdate correctly, he shouldn't be constructing a substantial number of descriptors for vdom diffing, right? cc @sebmarkbage

cc @jordwalke sounds like a situation similar to your react-native situation where you wanted to tunnel updates, right?

@zpao
Copy link
Member

zpao commented Mar 5, 2015

@JSFB you're still creating the elements before you go into the existing ones to run shouldComponentUpdate (we need to know if there's an update or if there's a replace/remove). So you need to create 1000 elements to know that you aren't updating any of them :/

One of the things we've been working hard towards is the elimination of the need for calling createElement if it can be optimized away. That and a couple other ideas for optimizations are being discussed in #3228, #3227, #3226

What @jeffchan said should work, though it can get a bit unwieldy. Anther option is to start windowing. You may not actually need 1000 elements to be rendered if 900 of them aren't actually visible. This is also a fair amount of work (you have to start tracking scrolling and sizes) but definitely doable. We did that with fixed data table.

@radubrehar
Copy link
Author

I went with windowing and only rendering the visible rows (and some extra to top and bottom), and with this I managed to have an infinite number of rows in the list, with instant performance.

I've read about the optimization ideas being discussed in the linked issues and they sound very cool. But in many (if not most) cases I will not be able to make all props as value types. I develop highly dynamic components, and most styles are configurable (eg: I have rowStyle; oddRowStyle, evenRowStyle for odd/even rows, etc) and as far as I've read value equality will not be applied in case where object maps are used (maybe tagging elements (as in #3227) will help here?
What happens if my list is inside a parent with a constant value type? As far as I understand that element (AND all its children) will be skipped from updating? That would indeed be very smart, and useful for scenarios where the whole app re-renders. So I will only be concerned with the situation when my component does a setState, in order to do its housekeeping. This allows developing highly configurable/dynamic components, while skipping them from update when they are used in app-specific (and often value-type compatible) components/containers.

I've tried caching element descriptors and that helps too, though windowing is a better solution in my scenario, since when the app re-renders I will only re-render a very limited set of children, while caching element descriptors is invalidated on props change.
So my problem is solved, but one day any of us can be in a situation where these optimizations are not possible, or are too ugly.
What if we could have a method to specify which keyed children should update, similar to shouldComponentUpdate?

shouldComponentUpdate: function(){
...
},

shouldKeyedChildrenUpdate: function(){
   ...
  return {
     'a23d45': bool1,
     'd75g63': bool2
  } //only update children with the keys present in the object, and with the values set to true
}

Just an idea that came quickly, not very polished, but maybe worth investigating?
Some drawbacks already came to mind with this idea (could only be used on state transition, not on component will receive props?; useless optimization for immutable data structures, since shouldComponent update is enough), but maybe there is some value in it.

@gaearon
Copy link
Collaborator

gaearon commented Oct 2, 2017

To fix this, I would suggest windowing (e.g. with react-virtualized) or, if you don't mind being lower level, caching React elements on your own. If a child is referentially equal React will skip reconciling it.

@gaearon gaearon closed this as completed Oct 2, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants