@@ -314,6 +314,12 @@ type State = {
314
314
last : number ,
315
315
} ;
316
316
317
+ type CachedCell = {
318
+ component : any ,
319
+ deps : Array < any > ,
320
+ used ?: boolean ,
321
+ } ;
322
+
317
323
/**
318
324
* Default Props Helper Functions
319
325
* Use the following helper functions for default values
@@ -784,8 +790,51 @@ class VirtualizedList extends React.PureComponent<Props, State> {
784
790
} ;
785
791
}
786
792
793
+ _cachedCells: Map < string , CachedCell > = new Map < string , CachedCell > ( ) ;
794
+ _cells = [ ] ;
795
+
796
+ _markCellsAsUnused ( ) {
797
+ this . _cachedCells . forEach ( cell => {
798
+ cell . used = false ;
799
+ } ) ;
800
+ }
801
+
802
+ _deleteUnusedCells ( ) {
803
+ this . _cachedCells . forEach ( ( cell , key ) => {
804
+ if ( ! cell . used ) {
805
+ this . _cachedCells . delete ( key ) ;
806
+ }
807
+ } ) ;
808
+ }
809
+
810
+ _pushCell ( key , deps , creator ) {
811
+ //$FlowFixMe[incompatible-type]
812
+ let cell : CachedCell = this . _cachedCells . get ( key ) ;
813
+ let depsChanged = false ;
814
+ if ( ! cell || cell . deps . length !== deps . length ) {
815
+ depsChanged = true ;
816
+ } else {
817
+ for ( let i = 0 ; i < deps . length ; i ++ ) {
818
+ if ( cell . deps [ i ] !== deps [ i ] ) {
819
+ depsChanged = true ;
820
+ break ;
821
+ }
822
+ }
823
+ }
824
+ if ( depsChanged ) {
825
+ this . _cachedCells . set (
826
+ key ,
827
+ ( cell = {
828
+ component : creator ( ) ,
829
+ deps,
830
+ } ) ,
831
+ ) ;
832
+ }
833
+ cell . used = true ;
834
+ this . _cells . push ( cell . component ) ;
835
+ }
836
+
787
837
_pushCells (
788
- cells : Array < Object > ,
789
838
stickyHeaderIndices : Array < number > ,
790
839
stickyIndicesFromProps : Set < number > ,
791
840
first : number ,
@@ -809,9 +858,19 @@ class VirtualizedList extends React.PureComponent<Props, State> {
809
858
const key = this . _keyExtractor ( item , ii ) ;
810
859
this . _indicesToKeys . set ( ii , key ) ;
811
860
if ( stickyIndicesFromProps . has ( ii + stickyOffset ) ) {
812
- stickyHeaderIndices . push ( cells . length ) ;
861
+ stickyHeaderIndices . push ( this . _cells . length ) ;
813
862
}
814
- cells . push (
863
+ const deps = [
864
+ CellRendererComponent ,
865
+ ItemSeparatorComponent ,
866
+ horizontal ,
867
+ ii ,
868
+ inversionStyle ,
869
+ item ,
870
+ prevCellKey ,
871
+ this . props ,
872
+ ] ;
873
+ this . _pushCell ( key , deps , ( ) => (
815
874
< CellRenderer
816
875
CellRendererComponent = { CellRendererComponent }
817
876
ItemSeparatorComponent = { ii < end ? ItemSeparatorComponent : undefined }
@@ -830,8 +889,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
830
889
ref = { ref => {
831
890
this . _cellRefs [ key ] = ref ;
832
891
} }
833
- /> ,
834
- ) ;
892
+ />
893
+ ) ) ;
835
894
prevCellKey = key ;
836
895
}
837
896
}
@@ -895,37 +954,46 @@ class VirtualizedList extends React.PureComponent<Props, State> {
895
954
? styles . horizontallyInverted
896
955
: styles . verticallyInverted
897
956
: null ;
898
- const cells = [ ] ;
957
+ this . _cells = [ ] ;
958
+ this . _markCellsAsUnused ( ) ;
899
959
const stickyIndicesFromProps = new Set ( this . props . stickyHeaderIndices ) ;
900
960
const stickyHeaderIndices = [ ] ;
901
961
if ( ListHeaderComponent ) {
902
962
if ( stickyIndicesFromProps . has ( 0 ) ) {
903
963
stickyHeaderIndices . push ( 0 ) ;
904
964
}
905
- const element = React . isValidElement ( ListHeaderComponent ) ? (
906
- ListHeaderComponent
907
- ) : (
908
- // $FlowFixMe[not-a-component]
909
- // $FlowFixMe[incompatible-type-arg]
910
- < ListHeaderComponent />
911
- ) ;
912
- cells . push (
913
- < VirtualizedListCellContextProvider
914
- cellKey = { this . _getCellKey ( ) + '-header' }
915
- key = "$header" >
916
- < View
917
- onLayout = { this . _onLayoutHeader }
918
- style = { StyleSheet . compose (
919
- inversionStyle ,
920
- this . props . ListHeaderComponentStyle ,
921
- ) } >
922
- {
923
- // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors
924
- element
925
- }
926
- </ View >
927
- </ VirtualizedListCellContextProvider > ,
928
- ) ;
965
+ const deps = [
966
+ inversionStyle ,
967
+ ListHeaderComponent ,
968
+ this . props . extraData ,
969
+ this . props . ListHeaderComponentStyle ,
970
+ ] ;
971
+ this . _pushCell ( '$header' , deps , ( ) => {
972
+ const element = React . isValidElement ( ListHeaderComponent ) ? (
973
+ ListHeaderComponent
974
+ ) : (
975
+ // $FlowFixMe[not-a-component]
976
+ // $FlowFixMe[incompatible-type-arg]
977
+ < ListHeaderComponent />
978
+ ) ;
979
+ return (
980
+ < VirtualizedListCellContextProvider
981
+ cellKey = { this . _getCellKey ( ) + '-header' }
982
+ key = "$header" >
983
+ < View
984
+ onLayout = { this . _onLayoutHeader }
985
+ style = { StyleSheet . compose (
986
+ inversionStyle ,
987
+ this . props . ListHeaderComponentStyle ,
988
+ ) } >
989
+ {
990
+ // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors
991
+ element
992
+ }
993
+ </ View >
994
+ </ VirtualizedListCellContextProvider >
995
+ ) ;
996
+ } ) ;
929
997
}
930
998
const itemCount = this . props . getItemCount ( data ) ;
931
999
if ( itemCount > 0 ) {
@@ -937,7 +1005,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
937
1005
: initialNumToRenderOrDefault ( this . props . initialNumToRender ) - 1 ;
938
1006
const { first, last} = this . state ;
939
1007
this . _pushCells (
940
- cells ,
941
1008
stickyHeaderIndices ,
942
1009
stickyIndicesFromProps ,
943
1010
0 ,
@@ -958,11 +1025,10 @@ class VirtualizedList extends React.PureComponent<Props, State> {
958
1025
stickyBlock . offset -
959
1026
initBlock . offset -
960
1027
( this . props . initialScrollIndex ? 0 : initBlock . length ) ;
961
- cells . push (
1028
+ this . _cells . push (
962
1029
< View key = "$sticky_lead" style = { { [ spacerKey ] : leadSpace } } /> ,
963
1030
) ;
964
1031
this . _pushCells (
965
- cells ,
966
1032
stickyHeaderIndices ,
967
1033
stickyIndicesFromProps ,
968
1034
ii ,
@@ -972,7 +1038,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
972
1038
const trailSpace =
973
1039
this . _getFrameMetricsApprox ( first ) . offset -
974
1040
( stickyBlock . offset + stickyBlock . length ) ;
975
- cells . push (
1041
+ this . _cells . push (
976
1042
< View key = "$sticky_trail" style = { { [ spacerKey ] : trailSpace } } /> ,
977
1043
) ;
978
1044
insertedStickySpacer = true ;
@@ -985,13 +1051,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
985
1051
const firstSpace =
986
1052
this . _getFrameMetricsApprox ( first ) . offset -
987
1053
( initBlock . offset + initBlock . length ) ;
988
- cells . push (
1054
+ this . _cells . push (
989
1055
< View key = "$lead_spacer" style = { { [ spacerKey ] : firstSpace } } /> ,
990
1056
) ;
991
1057
}
992
1058
}
993
1059
this . _pushCells (
994
- cells ,
995
1060
stickyHeaderIndices ,
996
1061
stickyIndicesFromProps ,
997
1062
firstAfterInitial ,
@@ -1019,7 +1084,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1019
1084
endFrame . offset +
1020
1085
endFrame . length -
1021
1086
( lastFrame . offset + lastFrame . length ) ;
1022
- cells . push (
1087
+ this . _cells . push (
1023
1088
< View key = "$tail_spacer" style = { { [ spacerKey ] : tailSpacerLength } } /> ,
1024
1089
) ;
1025
1090
}
@@ -1033,7 +1098,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1033
1098
// $FlowFixMe[incompatible-type-arg]
1034
1099
< ListEmptyComponent />
1035
1100
)): any);
1036
- cells.push(
1101
+
1102
+ this._cells.push(
1037
1103
React.cloneElement(element, {
1038
1104
key : '$empty' ,
1039
1105
onLayout : event => {
@@ -1047,30 +1113,38 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1047
1113
);
1048
1114
}
1049
1115
if ( ListFooterComponent ) {
1050
- const element = React . isValidElement ( ListFooterComponent ) ? (
1051
- ListFooterComponent
1052
- ) : (
1053
- // $FlowFixMe[not-a-component]
1054
- // $FlowFixMe[incompatible-type-arg]
1055
- < ListFooterComponent />
1056
- ) ;
1057
- cells . push (
1058
- < VirtualizedListCellContextProvider
1059
- cellKey = { this . _getFooterCellKey ( ) }
1060
- key = "$footer" >
1061
- < View
1062
- onLayout = { this . _onLayoutFooter }
1063
- style = { StyleSheet . compose (
1064
- inversionStyle ,
1065
- this . props . ListFooterComponentStyle ,
1066
- ) } >
1067
- {
1068
- // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors
1069
- element
1070
- }
1071
- </ View >
1072
- </ VirtualizedListCellContextProvider > ,
1073
- ) ;
1116
+ const deps = [
1117
+ inversionStyle ,
1118
+ ListFooterComponent ,
1119
+ this . props . extraData ,
1120
+ this . props . ListFooterComponentStyle ,
1121
+ ] ;
1122
+ this . _pushCell ( '$footer' , deps , ( ) => {
1123
+ const element = React . isValidElement ( ListFooterComponent ) ? (
1124
+ ListFooterComponent
1125
+ ) : (
1126
+ // $FlowFixMe[not-a-component]
1127
+ // $FlowFixMe[incompatible-type-arg]
1128
+ < ListFooterComponent />
1129
+ ) ;
1130
+ return (
1131
+ < VirtualizedListCellContextProvider
1132
+ cellKey = { this . _getFooterCellKey ( ) }
1133
+ key = "$footer" >
1134
+ < View
1135
+ onLayout = { this . _onLayoutFooter }
1136
+ style = { StyleSheet . compose (
1137
+ inversionStyle ,
1138
+ this . props . ListFooterComponentStyle ,
1139
+ ) } >
1140
+ {
1141
+ // $FlowFixMe[incompatible-type] - Typing ReactNativeComponent revealed errors
1142
+ element
1143
+ }
1144
+ </ View >
1145
+ </ VirtualizedListCellContextProvider >
1146
+ ) ;
1147
+ } ) ;
1074
1148
}
1075
1149
const scrollProps = {
1076
1150
...this . props ,
@@ -1117,11 +1191,12 @@ class VirtualizedList extends React.PureComponent<Props, State> {
1117
1191
{
1118
1192
ref : this . _captureScrollRef ,
1119
1193
} ,
1120
- cells ,
1194
+ this . _cells ,
1121
1195
) }
1122
1196
</ VirtualizedListContextProvider >
1123
1197
) ;
1124
1198
let ret = innerRet ;
1199
+ this . _deleteUnusedCells ( ) ;
1125
1200
if ( __DEV__ ) {
1126
1201
ret = (
1127
1202
< ScrollView . Context . Consumer >
@@ -1931,33 +2006,27 @@ type CellRendererProps = {
1931
2006
} ;
1932
2007
1933
2008
type CellRendererState = {
1934
- separatorProps : $ReadOnly < { |
1935
- highlighted : boolean ,
1936
- leadingItem : ?Item ,
1937
- | } > ,
2009
+ highlighted : boolean ,
2010
+ leadingItem : ?Item ,
1938
2011
...
1939
2012
} ;
1940
2013
1941
- class CellRenderer extends React . Component <
2014
+ class CellRenderer extends React . PureComponent <
1942
2015
CellRendererProps ,
1943
2016
CellRendererState,
1944
2017
> {
1945
2018
state = {
1946
- separatorProps : {
1947
- highlighted : false ,
1948
- leadingItem : this . props . item ,
1949
- } ,
2019
+ highlighted : false ,
2020
+ leadingItem : this . props . item ,
1950
2021
} ;
1951
2022
1952
2023
static getDerivedStateFromProps (
1953
2024
props : CellRendererProps ,
1954
2025
prevState : CellRendererState ,
1955
2026
) : ?CellRendererState {
1956
2027
return {
1957
- separatorProps : {
1958
- ...prevState . separatorProps ,
1959
- leadingItem : props . item ,
1960
- } ,
2028
+ ...prevState ,
2029
+ leadingItem : props . item ,
1961
2030
} ;
1962
2031
}
1963
2032
@@ -1987,7 +2056,8 @@ class CellRenderer extends React.Component<
1987
2056
1988
2057
updateSeparatorProps ( newProps : Object ) {
1989
2058
this . setState ( state => ( {
1990
- separatorProps : { ...state . separatorProps , ...newProps } ,
2059
+ ...state ,
2060
+ ...newProps ,
1991
2061
} ) ) ;
1992
2062
}
1993
2063
@@ -2060,7 +2130,7 @@ class CellRenderer extends React.Component<
2060
2130
// NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and
2061
2131
// called explicitly by `ScrollViewStickyHeader`.
2062
2132
const itemSeparator = ItemSeparatorComponent && (
2063
- < ItemSeparatorComponent { ...this . state . separatorProps } />
2133
+ < ItemSeparatorComponent { ...this . state } />
2064
2134
) ;
2065
2135
const cellStyle = inversionStyle
2066
2136
? horizontal
0 commit comments