Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Libraries/Components/ScrollView/ScrollViewStickyHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import StyleSheet from '../../StyleSheet/StyleSheet';
import Animated from '../../Animated/Animated';
import * as React from 'react';
import {useEffect, useMemo, useRef, useCallback} from 'react';
import VirtualizedListInjection from '../../Lists/VirtualizedListInjection';

const AnimatedView = Animated.View;

Expand Down Expand Up @@ -264,7 +265,11 @@ const ScrollViewStickyHeaderWithForwardedRef: React.AbstractComponent<
props.onLayout(event);
const child = React.Children.only(props.children);
if (child.props.onLayout) {
child.props.onLayout(event);
if (VirtualizedListInjection.useVLOptimization) {
child.props.onLayout(event, child.props.cellKey, child.props.index);
} else {
child.props.onLayout(event);
}
}
};

Expand Down
97 changes: 71 additions & 26 deletions Libraries/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ScrollView = require('../Components/ScrollView/ScrollView');
const StyleSheet = require('../StyleSheet/StyleSheet');
const View = require('../Components/View/View');
const ViewabilityHelper = require('./ViewabilityHelper');
import VirtualizedListInjection from './VirtualizedListInjection';

const flattenStyle = require('../StyleSheet/flattenStyle');
const infoLog = require('../Utilities/infoLog');
Expand Down Expand Up @@ -221,6 +222,7 @@ type OptionalProps = {|
* within half the visible length of the list.
*/
onEndReachedThreshold?: ?number,
onLayout?: ?(e: LayoutEvent) => void,
/**
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
* sure to also set the `refreshing` prop correctly.
Expand Down Expand Up @@ -810,27 +812,45 @@ class VirtualizedList extends React.PureComponent<Props, State> {
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
stickyHeaderIndices.push(cells.length);
}
cells.push(
<CellRenderer
CellRendererComponent={CellRendererComponent}
ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
cellKey={key}
fillRateHelper={this._fillRateHelper}
horizontal={horizontal}
index={ii}
inversionStyle={inversionStyle}
item={item}
key={key}
prevCellKey={prevCellKey}
onUpdateSeparators={this._onUpdateSeparators}
onLayout={e => this._onCellLayout(e, key, ii)}
onUnmount={this._onCellUnmount}
parentProps={this.props}
ref={ref => {
this._cellRefs[key] = ref;
}}
/>,
);
const cellRendererBaseProps: CellRendererBaseProps = {
CellRendererComponent: CellRendererComponent,
ItemSeparatorComponent: ii < end ? ItemSeparatorComponent : undefined,
cellKey: key,
fillRateHelper: this._fillRateHelper,
horizontal: horizontal,
index: ii,
inversionStyle: inversionStyle,
item: item,
key: key,
prevCellKey: prevCellKey,
onUpdateSeparators: this._onUpdateSeparators,
onUnmount: this._onCellUnmount,
extraData: extraData,
ref: ref => {
this._cellRefs[key] = ref;
},
};
if (VirtualizedListInjection.useVLOptimization) {
cells.push(
<CellRenderer
{...cellRendererBaseProps}
onLayout={this._onCellLayout}
getItemLayout={getItemLayout}
renderItem={renderItem}
ListItemComponent={ListItemComponent}
debug={debug}
/>,
);
} else {
cells.push(
<CellRenderer
{...cellRendererBaseProps}
experimentalVirtualizedListOpt={false}
onLayout={e => this._onCellLayout(e, key, ii)}
parentProps={this.props}
/>,
);
}
prevCellKey = key;
}
}
Expand Down Expand Up @@ -1269,7 +1289,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
};

_onCellLayout(e, cellKey, index) {
_onCellLayout = (e, cellKey, index): void => {
const layout = e.nativeEvent.layout;
const next = {
offset: this._selectOffset(layout),
Expand Down Expand Up @@ -1302,7 +1322,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {

this._computeBlankness();
this._updateViewableItems(this.props.data);
}
};

_onCellUnmount = (cellKey: string) => {
const curr = this._frames[cellKey];
Expand Down Expand Up @@ -1893,7 +1913,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
}
}

type CellRendererProps = {
type CellRendererBaseProps = {
CellRendererComponent?: ?React.ComponentType<any>,
ItemSeparatorComponent: ?React.ComponentType<
any | {highlighted: boolean, leadingItem: ?Item},
Expand Down Expand Up @@ -1992,6 +2012,15 @@ class CellRenderer extends React.Component<
this.props.onUnmount(this.props.cellKey);
}

_onLayout = (e): void => {
if (VirtualizedListInjection.useVLOptimization) {
this.props.onLayout &&
this.props.onLayout(e, this.props.cellKey, this.props.index);
} else {
this.props.onLayout && this.props.onLayout(e);
}
};

_renderElement(renderItem, ListItemComponent, item, index) {
if (renderItem && ListItemComponent) {
console.warn(
Expand Down Expand Up @@ -2037,9 +2066,25 @@ class CellRenderer extends React.Component<
item,
index,
inversionStyle,
parentProps,
} = this.props;
const {renderItem, getItemLayout, ListItemComponent} = parentProps;

let ListItemComponent: $PropertyType<OptionalProps, 'ListEmptyComponent'>;
let renderItem: $PropertyType<OptionalProps, 'renderItem'>;
let debug: $PropertyType<OptionalProps, 'debug'>;
let getItemLayout: $PropertyType<OptionalProps, 'getItemLayout'>;
if (this.props.experimentalVirtualizedListOpt === true) {
ListItemComponent = this.props.ListItemComponent;
renderItem = this.props.renderItem;
debug = this.props.debug;
getItemLayout = this.props.getItemLayout;
} else {
const parentProps = this.props.parentProps;
ListItemComponent = parentProps.ListItemComponent;
renderItem = parentProps.renderItem;
debug = parentProps.debug;
getItemLayout = parentProps.getItemLayout;
}

const element = this._renderElement(
renderItem,
ListItemComponent,
Expand Down
19 changes: 19 additions & 0 deletions Libraries/Lists/VirtualizedListInjection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

const experiments = {
useVLOptimization: false,
};

export function setUseVLOptimization() {
experiments.useVLOptimization = true;
}

export default (experiments: {useVLOptimization: boolean});
55 changes: 55 additions & 0 deletions Libraries/Lists/__tests__/VirtualizedList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,61 @@ describe('VirtualizedList', () => {
);
});

it('calls _onCellLayout properly', () => {
const items = [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}];
const mock = jest.fn();
const component = ReactTestRenderer.create(
<VirtualizedList
data={items}
renderItem={({item}) => <item value={item.key} />}
getItem={(data, index) => data[index]}
getItemCount={data => data.length}
/>,
);
const virtualList: VirtualizedList = component.getInstance();
virtualList._onCellLayout = mock;
component.update(
<VirtualizedList
data={[...items, {key: 'i4'}]}
renderItem={({item}) => <item value={item.key} />}
getItem={(data, index) => data[index]}
getItemCount={data => data.length}
/>,
);
const cell = virtualList._cellRefs.i4;
const event = {
nativeEvent: {layout: {x: 0, y: 0, width: 50, height: 50}},
};
cell._onLayout(event);
expect(mock).toHaveBeenCalledWith(event, 'i4', 3);
});

it('handles extraData correctly', () => {
const mock = jest.fn();
const listData = [{key: 'i0'}, {key: 'i1'}, {key: 'i2'}];
const getItem = (data, index) => data[index];
const getItemCount = data => data.length;
const component = ReactTestRenderer.create(
<VirtualizedList
data={listData}
renderItem={mock}
getItem={getItem}
getItemCount={getItemCount}
/>,
);

component.update(
<VirtualizedList
data={listData}
renderItem={mock}
getItem={getItem}
getItemCount={getItemCount}
extraData={{updated: true}}
/>,
);
expect(mock).toHaveBeenCalledTimes(6);
});

it('getScrollRef for case where it returns a ScrollView', () => {
const listRef = React.createRef(null);

Expand Down