From 84da4e54b14afe24966c7c7c25f25bb51caab801 Mon Sep 17 00:00:00 2001 From: Hamid Date: Thu, 8 Apr 2021 15:40:43 +0430 Subject: [PATCH] Optimize VirtualizedList --- Libraries/Lists/VirtualizedList.js | 230 ++++++++++++++++++----------- 1 file changed, 140 insertions(+), 90 deletions(-) diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index a7c1567b3f607a..a4f8a20ab28aa1 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -784,8 +784,50 @@ class VirtualizedList extends React.PureComponent { }; } + _cellsCache = new Map(); + _cells = []; + + _markCellsAsUnused() { + this._cellsCache.forEach(cell => { + cell.used = false; + }); + } + + _deleteUnusedCells() { + this._cellsCache.forEach((cell, key) => { + if (!cell.used) { + this._cellsCache.delete(key); + } + }); + } + + _pushCell(key, deps, creator) { + let cell = this._cellsCache.get(key); + let depsChanged = false; + if (!cell || cell.deps.length !== deps.length) { + depsChanged = true; + } else { + for (let i = 0; i < deps.length; i++) { + if (cell.deps[i] !== deps[i]) { + depsChanged = true; + break; + } + } + } + if (depsChanged) { + this._cellsCache.set( + key, + (cell = { + component: creator(), + deps, + }), + ); + } + cell.used = true; + this._cells.push(cell.component); + } + _pushCells( - cells: Array, stickyHeaderIndices: Array, stickyIndicesFromProps: Set, first: number, @@ -809,9 +851,19 @@ class VirtualizedList extends React.PureComponent { const key = this._keyExtractor(item, ii); this._indicesToKeys.set(ii, key); if (stickyIndicesFromProps.has(ii + stickyOffset)) { - stickyHeaderIndices.push(cells.length); + stickyHeaderIndices.push(this._cells.length); } - cells.push( + const deps = [ + CellRendererComponent, + ItemSeparatorComponent, + horizontal, + ii, + inversionStyle, + item, + prevCellKey, + this.props, + ]; + this._pushCell(key, deps, () => ( { ref={ref => { this._cellRefs[key] = ref; }} - />, - ); + /> + )); prevCellKey = key; } } @@ -895,37 +947,40 @@ class VirtualizedList extends React.PureComponent { ? styles.horizontallyInverted : styles.verticallyInverted : null; - const cells = []; + this._cells = []; + this._markCellsAsUnused(); const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices); const stickyHeaderIndices = []; if (ListHeaderComponent) { if (stickyIndicesFromProps.has(0)) { stickyHeaderIndices.push(0); } - const element = React.isValidElement(ListHeaderComponent) ? ( - ListHeaderComponent - ) : ( - // $FlowFixMe[not-a-component] - // $FlowFixMe[incompatible-type-arg] - - ); - cells.push( - - - { - // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors - element - } - - , - ); + this._pushCell('$header', [], () => { + const element = React.isValidElement(ListHeaderComponent) ? ( + ListHeaderComponent + ) : ( + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + + ); + return ( + + + { + // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors + element + } + + + ); + }); } const itemCount = this.props.getItemCount(data); if (itemCount > 0) { @@ -937,7 +992,6 @@ class VirtualizedList extends React.PureComponent { : initialNumToRenderOrDefault(this.props.initialNumToRender) - 1; const {first, last} = this.state; this._pushCells( - cells, stickyHeaderIndices, stickyIndicesFromProps, 0, @@ -958,11 +1012,10 @@ class VirtualizedList extends React.PureComponent { stickyBlock.offset - initBlock.offset - (this.props.initialScrollIndex ? 0 : initBlock.length); - cells.push( + this._cells.push( , ); this._pushCells( - cells, stickyHeaderIndices, stickyIndicesFromProps, ii, @@ -972,7 +1025,7 @@ class VirtualizedList extends React.PureComponent { const trailSpace = this._getFrameMetricsApprox(first).offset - (stickyBlock.offset + stickyBlock.length); - cells.push( + this._cells.push( , ); insertedStickySpacer = true; @@ -985,13 +1038,12 @@ class VirtualizedList extends React.PureComponent { const firstSpace = this._getFrameMetricsApprox(first).offset - (initBlock.offset + initBlock.length); - cells.push( + this._cells.push( , ); } } this._pushCells( - cells, stickyHeaderIndices, stickyIndicesFromProps, firstAfterInitial, @@ -1019,22 +1071,22 @@ class VirtualizedList extends React.PureComponent { endFrame.offset + endFrame.length - (lastFrame.offset + lastFrame.length); - cells.push( + this._cells.push( , ); } } else if (ListEmptyComponent) { - const element: React.Element = ((React.isValidElement( - ListEmptyComponent, - ) ? ( - ListEmptyComponent - ) : ( - // $FlowFixMe[not-a-component] - // $FlowFixMe[incompatible-type-arg] - - )): any); - cells.push( - React.cloneElement(element, { + this._pushCell('$empty', [], () => { + const element: React.Element = ((React.isValidElement( + ListEmptyComponent, + ) ? ( + ListEmptyComponent + ) : ( + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + + )): any); + return React.cloneElement(element, { key: '$empty', onLayout: event => { this._onLayoutEmpty(event); @@ -1043,34 +1095,36 @@ class VirtualizedList extends React.PureComponent { } }, style: StyleSheet.compose(inversionStyle, element.props.style), - }), - ); + }); + }); } if (ListFooterComponent) { - const element = React.isValidElement(ListFooterComponent) ? ( - ListFooterComponent - ) : ( - // $FlowFixMe[not-a-component] - // $FlowFixMe[incompatible-type-arg] - - ); - cells.push( - - - { - // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors - element - } - - , - ); + this._pushCell('$footer', [], () => { + const element = React.isValidElement(ListFooterComponent) ? ( + ListFooterComponent + ) : ( + // $FlowFixMe[not-a-component] + // $FlowFixMe[incompatible-type-arg] + + ); + return ( + + + { + // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors + element + } + + + ); + }); } const scrollProps = { ...this.props, @@ -1117,11 +1171,12 @@ class VirtualizedList extends React.PureComponent { { ref: this._captureScrollRef, }, - cells, + this._cells, )} ); let ret = innerRet; + this._deleteUnusedCells(); if (__DEV__) { ret = ( @@ -1931,22 +1986,18 @@ type CellRendererProps = { }; type CellRendererState = { - separatorProps: $ReadOnly<{| - highlighted: boolean, - leadingItem: ?Item, - |}>, + highlighted: boolean, + leadingItem: ?Item, ... }; -class CellRenderer extends React.Component< +class CellRenderer extends React.PureComponent< CellRendererProps, CellRendererState, > { state = { - separatorProps: { - highlighted: false, - leadingItem: this.props.item, - }, + highlighted: false, + leadingItem: this.props.item, }; static getDerivedStateFromProps( @@ -1954,10 +2005,8 @@ class CellRenderer extends React.Component< prevState: CellRendererState, ): ?CellRendererState { return { - separatorProps: { - ...prevState.separatorProps, - leadingItem: props.item, - }, + ...prevState, + leadingItem: props.item, }; } @@ -1987,7 +2036,8 @@ class CellRenderer extends React.Component< updateSeparatorProps(newProps: Object) { this.setState(state => ({ - separatorProps: {...state.separatorProps, ...newProps}, + ...state, + ...newProps, })); } @@ -2060,7 +2110,7 @@ class CellRenderer extends React.Component< // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and // called explicitly by `ScrollViewStickyHeader`. const itemSeparator = ItemSeparatorComponent && ( - + ); const cellStyle = inversionStyle ? horizontal