Fix and optimize VirtualizedList update triggers

Summary:
- If the initial render doesn't extend past `onEndReachedThreshold` it is likely that onEndReached won't get called until scroll, which can be a bad experience if the `initialNumToRender` is very close to the viewport height. This happens because when `onContentSizeChange`, `onLayout` may not have fired yet so we don't know what the `visibleLength` is. Fix is to also call `maybeCallOnEndReached` in `_onLayout` as well.

- We have an optimization that does hi-pri render window updates when scrolling quickly and the content reaches the edge of the viewport, but there is also an important case where the user has scrolled to the end of the content and is waiting for a network response. Once the new data comes in, we want to render it ASAP because the user is waiting for it. To solve this we refactor our scheduling code into a shared function that always checks if it should be a hi-pri update instead of just in `_onScroll`.

Reviewed By: bvaughn

Differential Revision: D4975314

fbshipit-source-id: 8d64832ecbcbdbac430a08a4018d7a32b2216a85
This commit is contained in:
Spencer Ahrens 2017-05-03 10:18:27 -07:00 committed by Facebook Github Bot
parent 5084e1ba0f
commit 52e50af56d
1 changed files with 34 additions and 27 deletions

View File

@ -347,7 +347,6 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
if (data !== this.props.data || extraData !== this.props.extraData) {
this._hasDataChangedSinceEndReached = true;
}
this._updateCellsToRenderBatcher.schedule();
}
_pushCells(
@ -513,7 +512,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
}
componentDidUpdate() {
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
}
_averageCellLength = 0;
@ -567,7 +566,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
this._averageCellLength = this._totalCellLength / this._totalCellsMeasured;
this._frames[cellKey] = next;
this._highestMeasuredFrameIndex = Math.max(this._highestMeasuredFrameIndex, index);
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
} else {
this._frames[cellKey].inLayout = true;
}
@ -584,7 +583,8 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
_onLayout = (e: Object) => {
this._scrollMetrics.visibleLength = this._selectLength(e.nativeEvent.layout);
this.props.onLayout && this.props.onLayout(e);
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
this._maybeCallOnEndReached();
};
_onLayoutFooter = (e) => {
@ -671,7 +671,7 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
this.props.onContentSizeChange(width, height);
}
this._scrollMetrics.contentLength = this._selectLength({height, width});
this._updateCellsToRenderBatcher.schedule();
this._scheduleCellsToRenderUpdate();
this._maybeCallOnEndReached();
};
@ -697,36 +697,43 @@ class VirtualizedList extends React.PureComponent<OptionalProps, Props, State> {
const dOffset = offset - this._scrollMetrics.offset;
const velocity = dOffset / dt;
this._scrollMetrics = {contentLength, dt, dOffset, offset, timestamp, velocity, visibleLength};
const {data, getItemCount, windowSize} = this.props;
this._updateViewableItems(data);
if (!data) {
this._updateViewableItems(this.props);
if (!this.props) {
return;
}
this._maybeCallOnEndReached();
const {first, last} = this.state;
if (velocity !== 0) {
this._fillRateHelper.activate();
}
this._computeBlankness();
const itemCount = getItemCount(data);
if ((first > 0 && velocity < 0) || (last < itemCount - 1 && velocity > 0)) {
const distanceToContentEdge = Math.min(
Math.abs(this._getFrameMetricsApprox(first).offset - offset),
Math.abs(this._getFrameMetricsApprox(last).offset - (offset + visibleLength)),
this._scheduleCellsToRenderUpdate();
};
_scheduleCellsToRenderUpdate() {
const {first, last} = this.state;
const {offset, visibleLength, velocity} = this._scrollMetrics;
const itemCount = this.props.getItemCount(this.props.data);
let hiPri = false;
if (first > 0 || last < itemCount - 1) {
const distTop = offset - this._getFrameMetricsApprox(first).offset;
const distBottom = this._getFrameMetricsApprox(last).offset - (offset + visibleLength);
const scrollingThreshold = this.props.onEndReachedThreshold * visibleLength / 2;
hiPri = (
Math.min(distTop, distBottom) < 0 ||
(velocity < -2 && distTop < scrollingThreshold) ||
(velocity > 2 && distBottom < scrollingThreshold)
);
const hiPri = distanceToContentEdge < (windowSize * visibleLength / 4);
}
if (hiPri) {
// Don't worry about interactions when scrolling quickly; focus on filling content as fast
// as possible.
this._updateCellsToRenderBatcher.dispose({abort: true});
this._updateCellsToRender();
return;
}
}
} else {
this._updateCellsToRenderBatcher.schedule();
};
}
}
_onScrollBeginDrag = (e): void => {
this._viewabilityHelper.recordInteraction();