Skip to content

Commit bd4bdfc

Browse files
azizhkfacebook-github-bot
authored andcommitted
Experimental VirtualList Optimization (#27115)
Summary: This is VirtualList Optimization done by christianbach in #21163 enabled behind a prop: `experimentalVirtualizedListOpt` with backward compatibility. Fixes #20174 based of jasekiw pull request #20208 ## Changelog // TODO: [CATEGORY] [TYPE] - Message Pull Request resolved: #27115 Test Plan: // TODO: Add tests related to backward compatibility. (Will need help) Differential Revision: D30095387 Pulled By: lunaleaps fbshipit-source-id: 1c41e9e52beeb79b56b19dfb12d896a2c4c49529
1 parent 363a8fb commit bd4bdfc

File tree

4 files changed

+151
-27
lines changed

4 files changed

+151
-27
lines changed

Libraries/Components/ScrollView/ScrollViewStickyHeader.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import StyleSheet from '../../StyleSheet/StyleSheet';
1515
import Animated from '../../Animated/Animated';
1616
import * as React from 'react';
1717
import {useEffect, useMemo, useRef, useCallback} from 'react';
18+
import VirtualizedListInjection from '../../Lists/VirtualizedListInjection';
1819

1920
const AnimatedView = Animated.View;
2021

@@ -264,7 +265,11 @@ const ScrollViewStickyHeaderWithForwardedRef: React.AbstractComponent<
264265
props.onLayout(event);
265266
const child = React.Children.only(props.children);
266267
if (child.props.onLayout) {
267-
child.props.onLayout(event);
268+
if (VirtualizedListInjection.useVLOptimization) {
269+
child.props.onLayout(event, child.props.cellKey, child.props.index);
270+
} else {
271+
child.props.onLayout(event);
272+
}
268273
}
269274
};
270275

Libraries/Lists/VirtualizedList.js

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const ScrollView = require('../Components/ScrollView/ScrollView');
1616
const StyleSheet = require('../StyleSheet/StyleSheet');
1717
const View = require('../Components/View/View');
1818
const ViewabilityHelper = require('./ViewabilityHelper');
19+
import VirtualizedListInjection from './VirtualizedListInjection';
1920

2021
const flattenStyle = require('../StyleSheet/flattenStyle');
2122
const infoLog = require('../Utilities/infoLog');
@@ -221,6 +222,7 @@ type OptionalProps = {|
221222
* within half the visible length of the list.
222223
*/
223224
onEndReachedThreshold?: ?number,
225+
onLayout?: ?(e: LayoutEvent) => void,
224226
/**
225227
* If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
226228
* sure to also set the `refreshing` prop correctly.
@@ -810,27 +812,45 @@ class VirtualizedList extends React.PureComponent<Props, State> {
810812
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
811813
stickyHeaderIndices.push(cells.length);
812814
}
813-
cells.push(
814-
<CellRenderer
815-
CellRendererComponent={CellRendererComponent}
816-
ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
817-
cellKey={key}
818-
fillRateHelper={this._fillRateHelper}
819-
horizontal={horizontal}
820-
index={ii}
821-
inversionStyle={inversionStyle}
822-
item={item}
823-
key={key}
824-
prevCellKey={prevCellKey}
825-
onUpdateSeparators={this._onUpdateSeparators}
826-
onLayout={e => this._onCellLayout(e, key, ii)}
827-
onUnmount={this._onCellUnmount}
828-
parentProps={this.props}
829-
ref={ref => {
830-
this._cellRefs[key] = ref;
831-
}}
832-
/>,
833-
);
815+
const cellRendererBaseProps: CellRendererBaseProps = {
816+
CellRendererComponent: CellRendererComponent,
817+
ItemSeparatorComponent: ii < end ? ItemSeparatorComponent : undefined,
818+
cellKey: key,
819+
fillRateHelper: this._fillRateHelper,
820+
horizontal: horizontal,
821+
index: ii,
822+
inversionStyle: inversionStyle,
823+
item: item,
824+
key: key,
825+
prevCellKey: prevCellKey,
826+
onUpdateSeparators: this._onUpdateSeparators,
827+
onUnmount: this._onCellUnmount,
828+
extraData: extraData,
829+
ref: ref => {
830+
this._cellRefs[key] = ref;
831+
},
832+
};
833+
if (VirtualizedListInjection.useVLOptimization) {
834+
cells.push(
835+
<CellRenderer
836+
{...cellRendererBaseProps}
837+
onLayout={this._onCellLayout}
838+
getItemLayout={getItemLayout}
839+
renderItem={renderItem}
840+
ListItemComponent={ListItemComponent}
841+
debug={debug}
842+
/>,
843+
);
844+
} else {
845+
cells.push(
846+
<CellRenderer
847+
{...cellRendererBaseProps}
848+
experimentalVirtualizedListOpt={false}
849+
onLayout={e => this._onCellLayout(e, key, ii)}
850+
parentProps={this.props}
851+
/>,
852+
);
853+
}
834854
prevCellKey = key;
835855
}
836856
}
@@ -1269,7 +1289,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
12691289
}
12701290
};
12711291

1272-
_onCellLayout(e, cellKey, index) {
1292+
_onCellLayout = (e, cellKey, index): void => {
12731293
const layout = e.nativeEvent.layout;
12741294
const next = {
12751295
offset: this._selectOffset(layout),
@@ -1302,7 +1322,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
13021322

13031323
this._computeBlankness();
13041324
this._updateViewableItems(this.props.data);
1305-
}
1325+
};
13061326

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

1896-
type CellRendererProps = {
1916+
type CellRendererBaseProps = {
18971917
CellRendererComponent?: ?React.ComponentType<any>,
18981918
ItemSeparatorComponent: ?React.ComponentType<
18991919
any | {highlighted: boolean, leadingItem: ?Item},
@@ -1992,6 +2012,15 @@ class CellRenderer extends React.Component<
19922012
this.props.onUnmount(this.props.cellKey);
19932013
}
19942014

2015+
_onLayout = (e): void => {
2016+
if (VirtualizedListInjection.useVLOptimization) {
2017+
this.props.onLayout &&
2018+
this.props.onLayout(e, this.props.cellKey, this.props.index);
2019+
} else {
2020+
this.props.onLayout && this.props.onLayout(e);
2021+
}
2022+
};
2023+
19952024
_renderElement(renderItem, ListItemComponent, item, index) {
19962025
if (renderItem && ListItemComponent) {
19972026
console.warn(
@@ -2037,9 +2066,25 @@ class CellRenderer extends React.Component<
20372066
item,
20382067
index,
20392068
inversionStyle,
2040-
parentProps,
20412069
} = this.props;
2042-
const {renderItem, getItemLayout, ListItemComponent} = parentProps;
2070+
2071+
let ListItemComponent: $PropertyType<OptionalProps, 'ListEmptyComponent'>;
2072+
let renderItem: $PropertyType<OptionalProps, 'renderItem'>;
2073+
let debug: $PropertyType<OptionalProps, 'debug'>;
2074+
let getItemLayout: $PropertyType<OptionalProps, 'getItemLayout'>;
2075+
if (this.props.experimentalVirtualizedListOpt === true) {
2076+
ListItemComponent = this.props.ListItemComponent;
2077+
renderItem = this.props.renderItem;
2078+
debug = this.props.debug;
2079+
getItemLayout = this.props.getItemLayout;
2080+
} else {
2081+
const parentProps = this.props.parentProps;
2082+
ListItemComponent = parentProps.ListItemComponent;
2083+
renderItem = parentProps.renderItem;
2084+
debug = parentProps.debug;
2085+
getItemLayout = parentProps.getItemLayout;
2086+
}
2087+
20432088
const element = this._renderElement(
20442089
renderItem,
20452090
ListItemComponent,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
const experiments = {
12+
useVLOptimization: false,
13+
};
14+
15+
export function setUseVLOptimization() {
16+
experiments.useVLOptimization = true;
17+
}
18+
19+
export default (experiments: {useVLOptimization: boolean});

Libraries/Lists/__tests__/VirtualizedList-test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,61 @@ describe('VirtualizedList', () => {
305305
);
306306
});
307307

308+
it('calls _onCellLayout properly', () => {
309+
const items = [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}];
310+
const mock = jest.fn();
311+
const component = ReactTestRenderer.create(
312+
<VirtualizedList
313+
data={items}
314+
renderItem={({item}) => <item value={item.key} />}
315+
getItem={(data, index) => data[index]}
316+
getItemCount={data => data.length}
317+
/>,
318+
);
319+
const virtualList: VirtualizedList = component.getInstance();
320+
virtualList._onCellLayout = mock;
321+
component.update(
322+
<VirtualizedList
323+
data={[...items, {key: 'i4'}]}
324+
renderItem={({item}) => <item value={item.key} />}
325+
getItem={(data, index) => data[index]}
326+
getItemCount={data => data.length}
327+
/>,
328+
);
329+
const cell = virtualList._cellRefs.i4;
330+
const event = {
331+
nativeEvent: {layout: {x: 0, y: 0, width: 50, height: 50}},
332+
};
333+
cell._onLayout(event);
334+
expect(mock).toHaveBeenCalledWith(event, 'i4', 3);
335+
});
336+
337+
it('handles extraData correctly', () => {
338+
const mock = jest.fn();
339+
const listData = [{key: 'i0'}, {key: 'i1'}, {key: 'i2'}];
340+
const getItem = (data, index) => data[index];
341+
const getItemCount = data => data.length;
342+
const component = ReactTestRenderer.create(
343+
<VirtualizedList
344+
data={listData}
345+
renderItem={mock}
346+
getItem={getItem}
347+
getItemCount={getItemCount}
348+
/>,
349+
);
350+
351+
component.update(
352+
<VirtualizedList
353+
data={listData}
354+
renderItem={mock}
355+
getItem={getItem}
356+
getItemCount={getItemCount}
357+
extraData={{updated: true}}
358+
/>,
359+
);
360+
expect(mock).toHaveBeenCalledTimes(6);
361+
});
362+
308363
it('getScrollRef for case where it returns a ScrollView', () => {
309364
const listRef = React.createRef(null);
310365

0 commit comments

Comments
 (0)