Improve scroll to top performance

Reviewed By: yungsters, bvaughn

Differential Revision: D4533070

fbshipit-source-id: 9458801aa679b830642f43be93f43bc08ef841cd
This commit is contained in:
Spencer Ahrens 2017-02-13 12:53:54 -08:00 committed by Facebook Github Bot
parent 7d51580479
commit 12b228c5d9
1 changed files with 38 additions and 26 deletions

View File

@ -51,9 +51,9 @@ type ItemComponentType = ReactClass<{item: Item, index: number}>;
/** /**
* Renders a virtual list of items given a data blob and accessor functions. Items that are outside * Renders a virtual list of items given a data blob and accessor functions. Items that are outside
* the render window are 'virtualized' e.g. unmounted or never rendered in the first place. This * the render window (except for the initial items at the top) are 'virtualized' e.g. unmounted or
* improves performance and saves memory for large data sets, but will reset state on items that * never rendered in the first place. This improves performance and saves memory for large data
* scroll too far out of the render window. * sets, but will reset state on items that scroll too far out of the render window.
* *
* TODO: Note that LayoutAnimation and sticky section headers both have bugs when used with this and * TODO: Note that LayoutAnimation and sticky section headers both have bugs when used with this and
* are therefor not supported, but new Animated impl might work? * are therefor not supported, but new Animated impl might work?
@ -104,6 +104,7 @@ type OptionalProps = {
* Set this true while waiting for new data from a refresh. * Set this true while waiting for new data from a refresh.
*/ */
refreshing?: boolean, refreshing?: boolean,
removeClippedSubviews?: boolean,
renderScrollComponent: (props: Object) => React.Element<*>, renderScrollComponent: (props: Object) => React.Element<*>,
shouldItemUpdate: ( shouldItemUpdate: (
props: {item: Item, index: number}, props: {item: Item, index: number},
@ -191,6 +192,7 @@ class VirtualizedList extends React.PureComponent {
maxToRenderPerBatch: 10, maxToRenderPerBatch: 10,
onEndReached: () => {}, onEndReached: () => {},
onEndReachedThreshold: 2, // multiples of length onEndReachedThreshold: 2, // multiples of length
removeClippedSubviews: true,
renderScrollComponent: (props: Props) => { renderScrollComponent: (props: Props) => {
if (props.onRefresh) { if (props.onRefresh) {
invariant( invariant(
@ -255,9 +257,32 @@ class VirtualizedList extends React.PureComponent {
this._updateCellsToRenderBatcher.schedule(); this._updateCellsToRenderBatcher.schedule();
} }
_pushCells(cells, first, last) {
const {SeparatorComponent, data, getItem, getItemCount, keyExtractor} = this.props;
const end = getItemCount(data) - 1;
last = Math.min(end, last);
for (let ii = first; ii <= last; ii++) {
const item = getItem(data, ii);
invariant(item, 'No item for index ' + ii);
const key = keyExtractor(item, ii);
cells.push(
<CellRenderer
cellKey={key}
index={ii}
item={item}
key={key}
onLayout={this._onCellLayout}
parentProps={this.props}
/>
);
if (SeparatorComponent && ii < end) {
cells.push(<SeparatorComponent key={'sep' + ii}/>);
}
}
}
render() { render() {
const {FooterComponent, HeaderComponent, SeparatorComponent} = this.props; const {FooterComponent, HeaderComponent} = this.props;
const {data, disableVirtualization, getItem, horizontal, keyExtractor} = this.props; const {data, disableVirtualization, horizontal} = this.props;
const cells = []; const cells = [];
if (HeaderComponent) { if (HeaderComponent) {
cells.push( cells.push(
@ -269,31 +294,18 @@ class VirtualizedList extends React.PureComponent {
const itemCount = this.props.getItemCount(data); const itemCount = this.props.getItemCount(data);
if (itemCount > 0) { if (itemCount > 0) {
_usedIndexForKey = false; _usedIndexForKey = false;
const lastInitialIndex = this.props.initialNumToRender - 1;
const {first, last} = this.state; const {first, last} = this.state;
if (!disableVirtualization && first > 0) { this._pushCells(cells, 0, lastInitialIndex);
const firstOffset = this._getFrameMetricsApprox(first).offset - this._headerLength; if (!disableVirtualization && first > lastInitialIndex) {
const initBlock = this._getFrameMetricsApprox(lastInitialIndex);
const firstSpace = this._getFrameMetricsApprox(first).offset -
(initBlock.offset + initBlock.length);
cells.push( cells.push(
<View key="$lead_spacer" style={{[!horizontal ? 'height' : 'width']: firstOffset}} /> <View key="$lead_spacer" style={{[!horizontal ? 'height' : 'width']: firstSpace}} />
); );
} }
for (let ii = first; ii <= last; ii++) { this._pushCells(cells, Math.max(lastInitialIndex + 1, first), last);
const item = getItem(data, ii);
invariant(item, 'No item for index ' + ii);
const key = keyExtractor(item, ii);
cells.push(
<CellRenderer
cellKey={key}
index={ii}
item={item}
key={key}
onLayout={this._onCellLayout}
parentProps={this.props}
/>
);
if (SeparatorComponent && ii < last) {
cells.push(<SeparatorComponent key={'sep' + ii}/>);
}
}
if (!this._hasWarned.keys && _usedIndexForKey) { if (!this._hasWarned.keys && _usedIndexForKey) {
console.warn( console.warn(
'VirtualizedList: missing keys for items, make sure to specify a key property on each ' + 'VirtualizedList: missing keys for items, make sure to specify a key property on each ' +