@@ -10,6 +10,7 @@ import {
10
10
getItemRelativeTop ,
11
11
getCompareItemRelativeTop ,
12
12
alignScrollTop ,
13
+ requireVirtual ,
13
14
} from './utils/itemUtil' ;
14
15
import { getIndexByStartLoc , findListDiffIndex } from './utils/algorithmUtil' ;
15
16
@@ -45,10 +46,15 @@ export interface ListProps<T> extends React.HTMLAttributes<any> {
45
46
itemKey : string ;
46
47
component ?: string | React . FC < any > | React . ComponentClass < any > ;
47
48
disabled ?: boolean ;
49
+
50
+ /** When `disabled`, trigger if changed item not render. */
51
+ onSkipRender ?: ( ) => void ;
48
52
}
49
53
54
+ type Status = 'NONE' | 'MEASURE_START' | 'MEASURE_DONE' | 'SWITCH_TO_VIRTUAL' | 'SWITCH_TO_RAW' ;
55
+
50
56
interface ListState < T > {
51
- status : 'NONE' | 'MEASURE_START' | 'MEASURE_DONE' ;
57
+ status : Status ;
52
58
53
59
scrollTop : number | null ;
54
60
/** Located item index */
@@ -63,6 +69,15 @@ interface ListState<T> {
63
69
* we need revert back to the located item index.
64
70
*/
65
71
startItemTop : number ;
72
+
73
+ /**
74
+ * Tell if is using virtual scroll
75
+ */
76
+ isVirtual : boolean ;
77
+ /**
78
+ * Only used when turn virtual list to raw list
79
+ */
80
+ cacheScroll ?: RelativeScroll ;
66
81
}
67
82
68
83
/**
@@ -92,16 +107,6 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
92
107
data : [ ] ,
93
108
} ;
94
109
95
- state : ListState < T > = {
96
- status : 'NONE' ,
97
- scrollTop : null ,
98
- itemIndex : 0 ,
99
- itemOffsetPtg : 0 ,
100
- startIndex : 0 ,
101
- endIndex : 0 ,
102
- startItemTop : 0 ,
103
- } ;
104
-
105
110
listRef = React . createRef < HTMLElement > ( ) ;
106
111
107
112
itemElements : { [ index : number ] : HTMLElement } = { } ;
@@ -123,6 +128,17 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
123
128
super ( props ) ;
124
129
125
130
this . cachedProps = props ;
131
+
132
+ this . state = {
133
+ status : 'NONE' ,
134
+ scrollTop : null ,
135
+ itemIndex : 0 ,
136
+ itemOffsetPtg : 0 ,
137
+ startIndex : 0 ,
138
+ endIndex : 0 ,
139
+ startItemTop : 0 ,
140
+ isVirtual : requireVirtual ( props . height , props . itemHeight , props . data . length ) ,
141
+ } ;
126
142
}
127
143
128
144
/**
@@ -141,13 +157,41 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
141
157
*/
142
158
public componentDidUpdate ( ) {
143
159
const { status } = this . state ;
144
- const { data, height, itemHeight, disabled } = this . props ;
160
+ const { data, height, itemHeight, disabled, onSkipRender } = this . props ;
145
161
const prevData : T [ ] = this . cachedProps . data || [ ] ;
146
162
147
- if ( disabled || ! this . listRef . current ) {
163
+ const changedItemIndex : number =
164
+ prevData . length !== data . length ? findListDiffIndex ( prevData , data , this . getItemKey ) : null ;
165
+
166
+ if ( disabled ) {
167
+ // Should trigger `onSkipRender` to tell that diff component is not render in the list
168
+ if ( data . length > prevData . length ) {
169
+ const { startIndex, endIndex } = this . state ;
170
+ if ( onSkipRender && ( changedItemIndex < startIndex || endIndex < changedItemIndex ) ) {
171
+ onSkipRender ( ) ;
172
+ }
173
+ }
148
174
return ;
149
175
}
150
176
177
+ const isVirtual = requireVirtual ( height , itemHeight , data . length ) ;
178
+ let nextStatus = status ;
179
+ if ( this . state . isVirtual !== isVirtual ) {
180
+ nextStatus = isVirtual ? 'SWITCH_TO_VIRTUAL' : 'SWITCH_TO_RAW' ;
181
+ this . setState ( {
182
+ isVirtual,
183
+ status : nextStatus ,
184
+ } ) ;
185
+
186
+ /**
187
+ * We will wait a tick to let list turn to virtual list.
188
+ * And then use virtual list sync logic to adjust the scroll.
189
+ */
190
+ if ( nextStatus === 'SWITCH_TO_VIRTUAL' ) {
191
+ return ;
192
+ }
193
+ }
194
+
151
195
if ( status === 'MEASURE_START' ) {
152
196
const { startIndex, itemIndex, itemOffsetPtg } = this . state ;
153
197
const { scrollTop } = this . listRef . current ;
@@ -177,13 +221,38 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
177
221
} ) ;
178
222
}
179
223
180
- /**
181
- * Re-calculate the item position since `data` length changed.
182
- * [IMPORTANT] We use relative position calculate here.
183
- */
184
- if ( prevData . length !== data . length && height ) {
224
+ if ( status === 'SWITCH_TO_RAW' ) {
225
+ /**
226
+ * After virtual list back to raw list,
227
+ * we update the `scrollTop` to real top instead of percentage top.
228
+ */
229
+ const {
230
+ cacheScroll : { itemIndex, relativeTop } ,
231
+ } = this . state ;
232
+ let rawTop = relativeTop ;
233
+ for ( let index = 0 ; index < itemIndex ; index += 1 ) {
234
+ rawTop -= this . itemElementHeights [ this . getIndexKey ( index ) ] || 0 ;
235
+ }
236
+
237
+ this . lockScroll = true ;
238
+ this . listRef . current . scrollTop = - rawTop ;
239
+
240
+ this . setState ( {
241
+ status : 'MEASURE_DONE' ,
242
+ } ) ;
243
+
244
+ requestAnimationFrame ( ( ) => {
245
+ requestAnimationFrame ( ( ) => {
246
+ this . lockScroll = false ;
247
+ } ) ;
248
+ } ) ;
249
+ } else if ( prevData . length !== data . length && height ) {
250
+ /**
251
+ * Re-calculate the item position since `data` length changed.
252
+ * [IMPORTANT] We use relative position calculate here.
253
+ */
254
+ let { itemIndex : originItemIndex } = this . state ;
185
255
const {
186
- itemIndex : originItemIndex ,
187
256
itemOffsetPtg : originItemOffsetPtg ,
188
257
startIndex : originStartIndex ,
189
258
endIndex : originEndIndex ,
@@ -194,21 +263,27 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
194
263
this . collectItemHeights ( ) ;
195
264
196
265
// 1. Get origin located item top
197
- const originLocatedItemRelativeTop = getItemRelativeTop ( {
198
- itemIndex : originItemIndex ,
199
- itemOffsetPtg : originItemOffsetPtg ,
200
- itemElementHeights : this . itemElementHeights ,
201
- scrollPtg : getScrollPercentage ( {
202
- scrollTop : originScrollTop ,
203
- scrollHeight : prevData . length * itemHeight ,
266
+ let originLocatedItemRelativeTop : number ;
267
+
268
+ if ( this . state . status === 'SWITCH_TO_VIRTUAL' ) {
269
+ originItemIndex = 0 ;
270
+ originLocatedItemRelativeTop = - this . state . scrollTop ;
271
+ } else {
272
+ originLocatedItemRelativeTop = getItemRelativeTop ( {
273
+ itemIndex : originItemIndex ,
274
+ itemOffsetPtg : originItemOffsetPtg ,
275
+ itemElementHeights : this . itemElementHeights ,
276
+ scrollPtg : getScrollPercentage ( {
277
+ scrollTop : originScrollTop ,
278
+ scrollHeight : prevData . length * itemHeight ,
279
+ clientHeight : this . listRef . current . clientHeight ,
280
+ } ) ,
204
281
clientHeight : this . listRef . current . clientHeight ,
205
- } ) ,
206
- clientHeight : this . listRef . current . clientHeight ,
207
- getItemKey : ( index : number ) => this . getIndexKey ( index , this . cachedProps ) ,
208
- } ) ;
282
+ getItemKey : ( index : number ) => this . getIndexKey ( index , this . cachedProps ) ,
283
+ } ) ;
284
+ }
209
285
210
286
// 2. Find the compare item
211
- const changedItemIndex : number = findListDiffIndex ( prevData , data , this . getItemKey ) ;
212
287
let originCompareItemIndex = changedItemIndex - 1 ;
213
288
// Use next one since there are not more item before removed
214
289
if ( originCompareItemIndex < 0 ) {
@@ -226,10 +301,22 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
226
301
itemElementHeights : this . itemElementHeights ,
227
302
} ) ;
228
303
229
- this . internalScrollTo ( {
230
- itemIndex : originCompareItemIndex ,
231
- relativeTop : originCompareItemTop ,
232
- } ) ;
304
+ if ( nextStatus === 'SWITCH_TO_RAW' ) {
305
+ /**
306
+ * We will record current measure relative item top and apply in raw list after list turned
307
+ */
308
+ this . setState ( {
309
+ cacheScroll : {
310
+ itemIndex : originCompareItemIndex ,
311
+ relativeTop : originCompareItemTop ,
312
+ } ,
313
+ } ) ;
314
+ } else {
315
+ this . internalScrollTo ( {
316
+ itemIndex : originCompareItemIndex ,
317
+ relativeTop : originCompareItemTop ,
318
+ } ) ;
319
+ }
233
320
}
234
321
235
322
this . cachedProps = this . props ;
@@ -268,6 +355,12 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
268
355
} ) ;
269
356
} ;
270
357
358
+ public onRawScroll = ( ) => {
359
+ const { scrollTop } = this . listRef . current ;
360
+
361
+ this . setState ( { scrollTop } ) ;
362
+ } ;
363
+
271
364
public getIndexKey = ( index : number , props ?: Partial < ListProps < T > > ) => {
272
365
const mergedProps = props || this . props ;
273
366
const { data = [ ] } = mergedProps ;
@@ -431,6 +524,7 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
431
524
} ;
432
525
433
526
public render ( ) {
527
+ const { isVirtual } = this . state ;
434
528
const {
435
529
style,
436
530
component : Component = 'div' ,
@@ -439,13 +533,19 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
439
533
data,
440
534
children,
441
535
itemKey,
536
+ onSkipRender,
442
537
...restProps
443
538
} = this . props ;
444
539
445
540
// Render pure list if not set height or height is enough for all items
446
- if ( typeof height !== 'number' || data . length * itemHeight <= height ) {
541
+ if ( ! isVirtual ) {
447
542
return (
448
- < Component style = { height ? { ...style , height, ...ScrollStyle } : style } { ...restProps } >
543
+ < Component
544
+ style = { height ? { ...style , height, ...ScrollStyle } : style }
545
+ { ...restProps }
546
+ onScroll = { this . onRawScroll }
547
+ ref = { this . listRef }
548
+ >
449
549
< Filler height = { height } > { this . renderChildren ( data , 0 , children ) } </ Filler >
450
550
</ Component >
451
551
) ;
0 commit comments