seatgeek / react-infinite

A browser-ready efficient scrolling container based on UITableView
Other
2.71k stars 273 forks source link

Reuse list item dom nodes #138

Open OpenGG opened 8 years ago

OpenGG commented 8 years ago

I created this fiddle, it was basically copied from react-infinite/examples/borderless-window

https://jsfiddle.net/9hpvuzdw/2/

As we scroll up and down for a few times, the info on top right keeps track of all list items created.

Articles now[19], total created[400] means there are 19 items in the dom tree, 400 items have been created and added to the dom tree but 381 of them have been removed.

This shows that the list item dom nodes can not be reuse, every time we scroll, there can be mutiple new nodes created.

Is this potential bottleneck of scrolling performance? What can I do to reuse list item dom nodes?

garetht commented 8 years ago

@OpenGG This seems to be caused by the use of keys on the ListItems. While the React documentation is not overly detailed on these matters, I'll try and describe the two options available without completely misinterpreting something.

The first is to provide keys for each of the list items. As the docs say,

When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused)

It seems that the mutation commands to get between sequence (1) and sequence (2) here:

Sequence 1

<ListItem key={1}/>
<ListItem key={2}/>
<ListItem key={3}/>

Sequence 2

<ListItem key={2}/>
<ListItem key={3}/>
<ListItem key={4}/>

will be remove key={1}, insert key={2}, which removes one article DOM node and its children and creates one article DOM node and its children.

The other option is to leave out the key, which if I am not wrong causes React to fall back on its pairwise diffing. You should be able to see this in the fiddle by removing the key attribute, which causes the number of DOM nodes created to drop drastically. The mutation commands for those two sequences are then, given then each List Item says List Item x, replace "List Item 1" with "List Item 2", replace "List Item 2" with "List Item 3", replace "List Item 3" with "List Item 4".

Depending on what precisely you are trying to do, one of those methods may better suit your needs.

OpenGG commented 8 years ago

Thank you, it did work like you said.

But neither "always create new nodes" nor "touch every existing node" is perfect enough for me.

I'm thinking of something minimizing dom changes: When user scroll down, resort top list items to bottom and change their content, keep the ones in the middle untouch.

<ListItem>0</ListItem>
<ListItem>1</ListItem>
<ListItem>2</ListItem>

When user scroll down, item 0 moves below item 2, and its conten changes to 3, minimizing dom operation. Further more, I'm thinking of utilizing css trasform to position list items, in this way a dom resort becames a transform change, avoiding reflows.

garetht commented 8 years ago

Yes, there is a perfect solution for DOM diffing, but it has not been adopted by the React authors because of intractable problems of time complexity, the best solutions taking more than quadratic time. Instead, less perfect solutions are settled for, and the inefficiency is a concession to getting things done in linear time with little noticeable difference.

I'm happy to accept a pull request making things transform changes, which has been done in other libraries, but we haven't made that optimization in the past because performance so far has been acceptable for our use cases.