mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +08:00
[change] Update VirtualizedList implementation
Mirror contents of React Native 0.57.5 Ref #1172
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Batchinator from '../Batchinator';
|
||||
import FillRateHelper from '../FillRateHelper';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -34,6 +36,8 @@ type DangerouslyImpreciseStyleProp = any;
|
||||
|
||||
type Item = any;
|
||||
|
||||
type ViewStyleProp = any;
|
||||
|
||||
export type renderItemType = (info: any) => ?React.Element<any>;
|
||||
|
||||
type ViewabilityHelperCallbackTuple = {
|
||||
@@ -118,11 +122,19 @@ type OptionalProps = {
|
||||
* a rendered element.
|
||||
*/
|
||||
ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListFooterComponent
|
||||
*/
|
||||
ListFooterComponentStyle?: ViewStyleProp,
|
||||
/**
|
||||
* Rendered at the top of all the items. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
*/
|
||||
ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
/**
|
||||
* Styling for internal View for ListHeaderComponent
|
||||
*/
|
||||
ListHeaderComponentStyle?: ViewStyleProp,
|
||||
/**
|
||||
* A unique identifier for this list. If there are multiple VirtualizedLists at the same level of
|
||||
* nesting within another VirtualizedList, this key is necessary for virtualization to
|
||||
@@ -166,6 +178,12 @@ type OptionalProps = {
|
||||
* @platform android
|
||||
*/
|
||||
progressViewOffset?: number,
|
||||
/**
|
||||
* A custom refresh control element. When set, it overrides the default
|
||||
* <RefreshControl> component built internally. The onRefresh and refreshing
|
||||
* props are also ignored. Only works for vertical VirtualizedList.
|
||||
*/
|
||||
refreshControl?: ?React.Element<any>,
|
||||
/**
|
||||
* Set this true while waiting for new data from a refresh.
|
||||
*/
|
||||
@@ -205,6 +223,7 @@ type OptionalProps = {
|
||||
export type Props = RequiredProps & OptionalProps;
|
||||
|
||||
let _usedIndexForKey = false;
|
||||
let _keylessItemComponentName: string = '';
|
||||
|
||||
type Frame = {
|
||||
offset: number,
|
||||
@@ -399,7 +418,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
||||
return this._scrollRef.getScrollableNode();
|
||||
} else {
|
||||
return findNodeHandle(this._scrollRef);
|
||||
return ReactNative.findNodeHandle(this._scrollRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +429,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
disableVirtualization: process.env.NODE_ENV === 'test',
|
||||
disableVirtualization: false,
|
||||
horizontal: false,
|
||||
initialNumToRender: 10,
|
||||
keyExtractor: (item: Item, index: number) => {
|
||||
@@ -418,6 +437,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
return item.key;
|
||||
}
|
||||
_usedIndexForKey = true;
|
||||
if (item.type && item.type.displayName) {
|
||||
_keylessItemComponentName = item.type.displayName;
|
||||
}
|
||||
return String(index);
|
||||
},
|
||||
maxToRenderPerBatch: 10,
|
||||
@@ -740,12 +762,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
<VirtualizedCellWrapper
|
||||
cellKey={this._getCellKey() + '-header'}
|
||||
key="$header">
|
||||
<View onLayout={this._onLayoutHeader} style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
<View
|
||||
onLayout={this._onLayoutHeader}
|
||||
style={StyleSheet.compose(
|
||||
inversionStyle,
|
||||
this.props.ListHeaderComponentStyle,
|
||||
)}>
|
||||
{
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
element
|
||||
}
|
||||
</View>
|
||||
</VirtualizedCellWrapper>,
|
||||
);
|
||||
@@ -753,6 +779,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
const itemCount = this.props.getItemCount(data);
|
||||
if (itemCount > 0) {
|
||||
_usedIndexForKey = false;
|
||||
_keylessItemComponentName = '';
|
||||
const spacerKey = !horizontal ? 'height' : 'width';
|
||||
const lastInitialIndex = this.props.initialScrollIndex
|
||||
? -1
|
||||
@@ -776,8 +803,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
||||
const initBlock = this._getFrameMetricsApprox(lastInitialIndex);
|
||||
const stickyBlock = this._getFrameMetricsApprox(ii);
|
||||
const leadSpace =
|
||||
stickyBlock.offset - (initBlock.offset + initBlock.length);
|
||||
const leadSpace = stickyBlock.offset - initBlock.offset;
|
||||
cells.push(
|
||||
<View key="$sticky_lead" style={{[spacerKey]: leadSpace}} />,
|
||||
);
|
||||
@@ -822,6 +848,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
console.warn(
|
||||
'VirtualizedList: missing keys for items, make sure to specify a key property on each ' +
|
||||
'item or provide a custom keyExtractor.',
|
||||
_keylessItemComponentName,
|
||||
);
|
||||
this._hasWarned.keys = true;
|
||||
}
|
||||
@@ -843,23 +870,25 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
} else if (ListEmptyComponent) {
|
||||
const element = React.isValidElement(ListEmptyComponent) ? (
|
||||
const element: React.Element<any> = ((React.isValidElement(
|
||||
ListEmptyComponent,
|
||||
) ? (
|
||||
ListEmptyComponent
|
||||
) : (
|
||||
// $FlowFixMe
|
||||
<ListEmptyComponent />
|
||||
);
|
||||
)): any);
|
||||
cells.push(
|
||||
<View
|
||||
key="$empty"
|
||||
onLayout={this._onLayoutEmpty}
|
||||
style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
</View>,
|
||||
React.cloneElement(element, {
|
||||
key: '$empty',
|
||||
onLayout: event => {
|
||||
this._onLayoutEmpty(event);
|
||||
if (element.props.onLayout) {
|
||||
element.props.onLayout(event);
|
||||
}
|
||||
},
|
||||
style: [element.props.style, inversionStyle],
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (ListFooterComponent) {
|
||||
@@ -873,12 +902,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
<VirtualizedCellWrapper
|
||||
cellKey={this._getCellKey() + '-footer'}
|
||||
key="$footer">
|
||||
<View onLayout={this._onLayoutFooter} style={inversionStyle}>
|
||||
{/*
|
||||
Flow doesn't know this is a React.Element and not a React.Component
|
||||
$FlowFixMe https://fburl.com/b9xmtm09
|
||||
*/}
|
||||
{element}
|
||||
<View
|
||||
onLayout={this._onLayoutFooter}
|
||||
style={StyleSheet.compose(
|
||||
inversionStyle,
|
||||
this.props.ListFooterComponentStyle,
|
||||
)}>
|
||||
{
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
element
|
||||
}
|
||||
</View>
|
||||
</VirtualizedCellWrapper>,
|
||||
);
|
||||
@@ -892,12 +925,16 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
onScrollEndDrag: this._onScrollEndDrag,
|
||||
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
||||
scrollEventThrottle: this.props.scrollEventThrottle, // TODO: Android support
|
||||
invertStickyHeaders: this.props.invertStickyHeaders !== undefined
|
||||
? this.props.invertStickyHeaders
|
||||
: this.props.inverted,
|
||||
invertStickyHeaders:
|
||||
this.props.invertStickyHeaders !== undefined
|
||||
? this.props.invertStickyHeaders
|
||||
: this.props.inverted,
|
||||
stickyHeaderIndices,
|
||||
};
|
||||
if (inversionStyle) {
|
||||
/* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.70 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
scrollProps.style = [inversionStyle, this.props.style];
|
||||
}
|
||||
|
||||
@@ -908,6 +945,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
(this.props.renderScrollComponent || this._defaultRenderScrollComponent)(
|
||||
scrollProps,
|
||||
),
|
||||
// $FlowFixMe Invalid prop usage
|
||||
{
|
||||
ref: this._captureScrollRef,
|
||||
},
|
||||
@@ -989,9 +1027,11 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_defaultRenderScrollComponent = props => {
|
||||
const onRefresh = props.onRefresh;
|
||||
if (this._isNestedWithSameOrientation()) {
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
return <View {...props} />;
|
||||
} else if (props.onRefresh) {
|
||||
} else if (onRefresh) {
|
||||
invariant(
|
||||
typeof props.refreshing === 'boolean',
|
||||
'`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' +
|
||||
@@ -999,21 +1039,24 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
'`',
|
||||
);
|
||||
return (
|
||||
// $FlowFixMe Invalid prop usage
|
||||
<ScrollView
|
||||
{...props}
|
||||
refreshControl={
|
||||
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */
|
||||
<RefreshControl
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={props.onRefresh}
|
||||
progressViewOffset={props.progressViewOffset}
|
||||
/>
|
||||
props.refreshControl == null ? (
|
||||
<RefreshControl
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={onRefresh}
|
||||
progressViewOffset={props.progressViewOffset}
|
||||
/>
|
||||
) : (
|
||||
props.refreshControl
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// $FlowFixMe Invalid prop usage
|
||||
return <ScrollView {...props} />;
|
||||
}
|
||||
};
|
||||
@@ -1046,6 +1089,17 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
} else {
|
||||
this._frames[cellKey].inLayout = true;
|
||||
}
|
||||
|
||||
const childListKeys = this._cellKeysToChildListKeys.get(cellKey);
|
||||
if (childListKeys) {
|
||||
for (let childKey of childListKeys) {
|
||||
const childList = this._nestedChildLists.get(childKey);
|
||||
childList &&
|
||||
childList.ref &&
|
||||
childList.ref.measureLayoutRelativeToContainingList();
|
||||
}
|
||||
}
|
||||
|
||||
this._computeBlankness();
|
||||
}
|
||||
|
||||
@@ -1056,36 +1110,48 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
_measureLayoutRelativeToContainingList(): void {
|
||||
UIManager.measureLayout(
|
||||
findNodeHandle(this),
|
||||
findNodeHandle(
|
||||
this.context.virtualizedList.getOutermostParentListRef(),
|
||||
),
|
||||
error => {
|
||||
console.warn(
|
||||
"VirtualizedList: Encountered an error while measuring a list's" +
|
||||
' offset from its containing VirtualizedList.',
|
||||
);
|
||||
},
|
||||
(x, y, width, height) => {
|
||||
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
||||
this._scrollMetrics.contentLength = this._selectLength({width, height});
|
||||
measureLayoutRelativeToContainingList(): void {
|
||||
// TODO (T35574538): findNodeHandle sometimes crashes with "Unable to find
|
||||
// node on an unmounted component" during scrolling
|
||||
try {
|
||||
UIManager.measureLayout(
|
||||
ReactNative.findNodeHandle(this),
|
||||
ReactNative.findNodeHandle(
|
||||
this.context.virtualizedList.getOutermostParentListRef(),
|
||||
),
|
||||
error => {
|
||||
console.warn(
|
||||
"VirtualizedList: Encountered an error while measuring a list's" +
|
||||
' offset from its containing VirtualizedList.',
|
||||
);
|
||||
},
|
||||
(x, y, width, height) => {
|
||||
this._offsetFromParentVirtualizedList = this._selectOffset({x, y});
|
||||
this._scrollMetrics.contentLength = this._selectLength({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
const scrollMetrics = this._convertParentScrollMetrics(
|
||||
this.context.virtualizedList.getScrollMetrics(),
|
||||
);
|
||||
this._scrollMetrics.visibleLength = scrollMetrics.visibleLength;
|
||||
this._scrollMetrics.offset = scrollMetrics.offset;
|
||||
},
|
||||
);
|
||||
const scrollMetrics = this._convertParentScrollMetrics(
|
||||
this.context.virtualizedList.getScrollMetrics(),
|
||||
);
|
||||
this._scrollMetrics.visibleLength = scrollMetrics.visibleLength;
|
||||
this._scrollMetrics.offset = scrollMetrics.offset;
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'measureLayoutRelativeToContainingList threw an error',
|
||||
error.stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_onLayout = (e: Object) => {
|
||||
if (this._isNestedWithSameOrientation()) {
|
||||
// Need to adjust our scroll metrics to be relative to our containing
|
||||
// VirtualizedList before we can make claims about list item viewability
|
||||
this._measureLayoutRelativeToContainingList();
|
||||
this.measureLayoutRelativeToContainingList();
|
||||
} else {
|
||||
this._scrollMetrics.visibleLength = this._selectLength(
|
||||
e.nativeEvent.layout,
|
||||
@@ -1115,6 +1181,9 @@ class VirtualizedList extends React.PureComponent<Props, State> {
|
||||
const itemCount = this.props.getItemCount(this.props.data);
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const frame = this._getFrameMetricsApprox(ii);
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
if (frame.inLayout) {
|
||||
framesInLayout.push(frame);
|
||||
}
|
||||
@@ -1323,18 +1392,26 @@ class VirtualizedList extends React.PureComponent<Props, 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 scrollingThreshold =
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.63 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
(this.props.onEndReachedThreshold * visibleLength) / 2;
|
||||
// Mark as high priority if we're close to the start of the first item
|
||||
// But only if there are items before the first rendered item
|
||||
if (first > 0) {
|
||||
const distTop = offset - this._getFrameMetricsApprox(first).offset;
|
||||
hiPri =
|
||||
hiPri || distTop < 0 || (velocity < -2 && distTop < scrollingThreshold);
|
||||
}
|
||||
// Mark as high priority if we're close to the end of the last item
|
||||
// But only if there are items after the last rendered item
|
||||
if (last < itemCount - 1) {
|
||||
const distBottom =
|
||||
this._getFrameMetricsApprox(last).offset - (offset + visibleLength);
|
||||
const scrollingThreshold =
|
||||
/* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.63 was deployed. To see the error delete
|
||||
* this comment and run Flow. */
|
||||
this.props.onEndReachedThreshold * visibleLength / 2;
|
||||
hiPri =
|
||||
Math.min(distTop, distBottom) < 0 ||
|
||||
(velocity < -2 && distTop < scrollingThreshold) ||
|
||||
hiPri ||
|
||||
distBottom < 0 ||
|
||||
(velocity > 2 && distBottom < scrollingThreshold);
|
||||
}
|
||||
// Only trigger high-priority updates if we've actually rendered cells,
|
||||
@@ -1562,6 +1639,7 @@ class CellRenderer extends React.Component<
|
||||
renderItem: renderItemType,
|
||||
},
|
||||
prevCellKey: ?string
|
||||
prevCellKey: ?string,
|
||||
},
|
||||
$FlowFixMeState,
|
||||
> {
|
||||
@@ -1639,6 +1717,9 @@ class CellRenderer extends React.Component<
|
||||
separators: this._separators,
|
||||
});
|
||||
const onLayout =
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
* comment and run Flow. */
|
||||
getItemLayout && !parentProps.debug && !fillRateHelper.enabled()
|
||||
? undefined
|
||||
: this.props.onLayout;
|
||||
@@ -1702,15 +1783,6 @@ const styles = StyleSheet.create({
|
||||
horizontallyInverted: {
|
||||
transform: [{scaleX: -1}],
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
rowReverse: {
|
||||
flexDirection: 'row-reverse'
|
||||
},
|
||||
columnReverse: {
|
||||
flexDirection: 'column-reverse'
|
||||
}
|
||||
});
|
||||
|
||||
export default VirtualizedList;
|
||||
|
||||
Reference in New Issue
Block a user