Skip to content

Commit 01ed1f0

Browse files
authored
refactor: opt getSize speed (#222)
* refactor: opt getSize speed * chore: useMemo
1 parent cb8d647 commit 01ed1f0

File tree

5 files changed

+76
-41
lines changed

5 files changed

+76
-41
lines changed

examples/horizontal-scroll.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const MyItem: React.ForwardRefRenderFunction<
7171
const ForwardMyItem = React.forwardRef(MyItem);
7272

7373
const data: Item[] = [];
74-
for (let i = 0; i < 1000; i += 1) {
74+
for (let i = 0; i < 10000; i += 1) {
7575
data.push({
7676
id: `id_${i}`,
7777
height: 30 + Math.random() * 10,
@@ -105,17 +105,15 @@ const Demo = () => {
105105
boxSizing: 'border-box',
106106
}}
107107
extraRender={(info) => {
108-
const { offsetX, offsetY, rtl: isRTL } = info;
109-
const sizeInfo = info.getSize('id_3', 'id_5');
108+
const { offsetY, rtl: isRTL } = info;
109+
const sizeInfo = info.getSize('id_5', 'id_10');
110110

111111
return (
112112
<div
113113
style={{
114114
position: 'absolute',
115-
// top: -offsetY,
116115
top: -offsetY + sizeInfo.top,
117116
height: sizeInfo.bottom - sizeInfo.top,
118-
// [isRTL ? 'right' : 'left']: 100 - offsetX,
119117
[isRTL ? 'right' : 'left']: 100,
120118
background: 'rgba(255,0,0,0.9)',
121119
zIndex: 1,

src/List.tsx

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import useOriginScroll from './hooks/useOriginScroll';
2020
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
2121
import { getSpinSize } from './utils/scrollbarUtil';
2222
import { useEvent } from 'rc-util';
23+
import { useGetSize } from './hooks/useGetSize';
2324

2425
const EMPTY_DATA = [];
2526

@@ -261,14 +262,14 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
261262
const verticalScrollBarRef = useRef<ScrollBarRef>();
262263
const horizontalScrollBarRef = useRef<ScrollBarRef>();
263264

264-
const horizontalScrollBarSpinSize = React.useMemo(() => getSpinSize(size.width, scrollWidth), [
265-
size.width,
266-
scrollWidth,
267-
]);
268-
const verticalScrollBarSpinSize = React.useMemo(() => getSpinSize(size.height, scrollHeight), [
269-
size.height,
270-
scrollHeight,
271-
]);
265+
const horizontalScrollBarSpinSize = React.useMemo(
266+
() => getSpinSize(size.width, scrollWidth),
267+
[size.width, scrollWidth],
268+
);
269+
const verticalScrollBarSpinSize = React.useMemo(
270+
() => getSpinSize(size.height, scrollHeight),
271+
[size.height, scrollHeight],
272+
);
272273

273274
// =============================== In Range ===============================
274275
const maxScrollHeight = scrollHeight - height;
@@ -456,31 +457,7 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
456457
}, [start, end, mergedData]);
457458

458459
// ================================ Extra =================================
459-
const getSize = (startKey: React.Key, endKey = startKey) => {
460-
let top = 0;
461-
let bottom = 0;
462-
let total = 0;
463-
464-
const dataLen = mergedData.length;
465-
for (let i = 0; i < dataLen; i += 1) {
466-
const item = mergedData[i];
467-
const key = getKey(item);
468-
469-
const cacheHeight = heights.get(key) ?? itemHeight;
470-
bottom = total + cacheHeight;
471-
472-
if (key === startKey) {
473-
top = total;
474-
}
475-
if (key === endKey) {
476-
break;
477-
}
478-
479-
total = bottom;
480-
}
481-
482-
return { top, bottom };
483-
};
460+
const getSize = useGetSize(mergedData, getKey, heights, itemHeight);
484461

485462
const extraContent = extraRender?.({
486463
start,

src/hooks/useGetSize.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type CacheMap from '../utils/CacheMap';
2+
import type { GetKey, GetSize } from '../interface';
3+
import * as React from 'react';
4+
5+
/**
6+
* Size info need loop query for the `heights` which will has the perf issue.
7+
* Let cache result for each render phase.
8+
*/
9+
export function useGetSize<T>(
10+
mergedData: T[],
11+
getKey: GetKey<T>,
12+
heights: CacheMap,
13+
itemHeight: number,
14+
) {
15+
const [key2Index, bottomList] = React.useMemo<
16+
[key2Index: Map<React.Key, number>, bottomList: number[]]
17+
>(() => [new Map(), []], [mergedData, heights.id, itemHeight]);
18+
19+
const getSize: GetSize = (startKey, endKey = startKey) => {
20+
// Get from cache first
21+
let startIndex = key2Index.get(startKey);
22+
let endIndex = key2Index.get(endKey);
23+
24+
// Loop to fill the cache
25+
if (startIndex === undefined || endIndex === undefined) {
26+
const dataLen = mergedData.length;
27+
for (let i = bottomList.length; i < dataLen; i += 1) {
28+
const item = mergedData[i];
29+
const key = getKey(item);
30+
key2Index.set(key, i);
31+
const cacheHeight = heights.get(key) ?? itemHeight;
32+
bottomList[i] = (bottomList[i - 1] || 0) + cacheHeight;
33+
if (key === startKey) {
34+
startIndex = i;
35+
}
36+
if (key === endKey) {
37+
endIndex = i;
38+
}
39+
40+
if (startIndex !== undefined && endIndex !== undefined) {
41+
break;
42+
}
43+
}
44+
}
45+
46+
return {
47+
top: bottomList[startIndex - 1] || 0,
48+
bottom: bottomList[endIndex],
49+
};
50+
};
51+
52+
return getSize;
53+
}

src/interface.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface SharedConfig<T> {
1010

1111
export type GetKey<T> = (item: T) => React.Key;
1212

13+
export type GetSize = (startKey: React.Key, endKey?: React.Key) => { top: number; bottom: number };
14+
1315
export interface ExtraRenderInfo {
1416
/** Virtual list start line */
1517
start: number;
@@ -23,5 +25,5 @@ export interface ExtraRenderInfo {
2325

2426
rtl: boolean;
2527

26-
getSize: (startKey: React.Key, endKey?: React.Key) => { top: number; bottom: number };
28+
getSize: GetSize;
2729
}

src/utils/CacheMap.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ import type React from 'react';
44
class CacheMap {
55
maps: Record<string, number>;
66

7+
// Used for cache key
8+
// `useMemo` no need to update if `id` not change
9+
id: number = 0;
10+
711
constructor() {
812
this.maps = Object.create(null);
913
}
1014

11-
set(key: React.ReactText, value: number) {
15+
set(key: React.Key, value: number) {
1216
this.maps[key] = value;
17+
this.id += 1;
1318
}
1419

15-
get(key: React.ReactText) {
20+
get(key: React.Key) {
1621
return this.maps[key];
1722
}
1823
}

0 commit comments

Comments
 (0)