Skip to content

Commit bc0bb55

Browse files
committed
1.3.9.08
【优化】 - 虚拟列表性能
1 parent 4a1ddda commit bc0bb55

2 files changed

Lines changed: 71 additions & 62 deletions

File tree

src/components/file/FileListRow.tsx

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useEffect, useRef, useState } from "react";
21
import type { CSSProperties, ReactElement } from "react";
32
import { motion } from "framer-motion";
43
import type { MotionStyle } from "framer-motion";
@@ -14,8 +13,6 @@ const RowComponent = ({
1413
style,
1514
...rowData
1615
}: RowComponentProps<VirtualListItemData>): ReactElement => {
17-
const rowRef = useRef<HTMLDivElement>(null);
18-
const [isVisible, setIsVisible] = useState(true);
1916
const {
2017
contents,
2118
downloadingPath,
@@ -34,38 +31,10 @@ const RowComponent = ({
3431

3532
const item = contents[index];
3633

37-
useEffect(() => {
38-
const element = rowRef.current;
39-
if (element === null) {
40-
return;
41-
}
42-
43-
const scrollContainer = element.closest('.virtual-file-list');
44-
45-
const observer = new IntersectionObserver(
46-
(entries) => {
47-
entries.forEach((entry) => {
48-
setIsVisible(entry.isIntersecting);
49-
});
50-
},
51-
{
52-
root: scrollContainer ?? null,
53-
rootMargin: "100px",
54-
threshold: 0.01,
55-
}
56-
);
57-
58-
observer.observe(element);
59-
60-
return () => {
61-
observer.disconnect();
62-
};
63-
}, []);
6434

6535
if (item === undefined) {
6636
return (
6737
<div
68-
ref={rowRef}
6938
style={style}
7039
{...ariaAttributes}
7140
aria-hidden="true"
@@ -95,11 +64,9 @@ const RowComponent = ({
9564

9665
return (
9766
<div
98-
ref={rowRef}
9967
style={adjustedStyle}
10068
className="file-list-item-container"
10169
{...ariaAttributes}
102-
aria-hidden={!isVisible ? "true" : undefined}
10370
data-oid="_c:db-1"
10471
>
10572
<motion.div
@@ -122,7 +89,6 @@ const RowComponent = ({
12289
currentPath={currentPath}
12390
contents={contents}
12491
isHighlighted={isHighlighted}
125-
isVisible={isVisible}
12692
data-oid="k4zj3qr"
12793
/>
12894
</motion.div>

src/hooks/useScroll.ts

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useRef, useCallback, useState } from 'react';
1+
import { useRef, useCallback, useState, useEffect } from 'react';
22

33
/**
44
* 滚动数据点接口
@@ -59,6 +59,7 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
5959
scrollEndDelay = 150,
6060
fastScrollThreshold = 0.3
6161
} = options;
62+
const speedEpsilon = 0.001;
6263

6364
// 滚动状态
6465
const [isScrolling, setIsScrolling] = useState(false);
@@ -68,11 +69,28 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
6869
const scrollDataRef = useRef<{
6970
positions: ScrollDataPoint[];
7071
timer: NodeJS.Timeout | null;
72+
rafId: number | null;
73+
isScrolling: boolean;
74+
speed: number;
7175
}>({
7276
positions: [],
73-
timer: null
77+
timer: null,
78+
rafId: null,
79+
isScrolling: false,
80+
speed: 0
7481
});
7582

83+
const recordSample = useCallback((offset: number): void => {
84+
const now = Date.now();
85+
const data = scrollDataRef.current;
86+
87+
data.positions.push({ offset, time: now });
88+
89+
if (data.positions.length > maxSamples) {
90+
data.positions.shift();
91+
}
92+
}, [maxSamples]);
93+
7694
/**
7795
* 计算滚动速度
7896
*
@@ -81,18 +99,9 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
8199
* @param offset - 当前滚动偏移量
82100
* @returns 标准化的滚动速度(像素/毫秒)
83101
*/
84-
const calculateSpeed = useCallback((offset: number): number => {
85-
const now = Date.now();
102+
const calculateSpeed = useCallback((): number => {
86103
const data = scrollDataRef.current;
87-
88-
// 添加新样本
89-
data.positions.push({ offset, time: now });
90-
91-
// 保持固定数量的样本(FIFO)
92-
if (data.positions.length > maxSamples) {
93-
data.positions.shift();
94-
}
95-
104+
96105
// 需要至少2个样本才能计算速度
97106
if (data.positions.length < 2) {
98107
return 0;
@@ -111,36 +120,50 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
111120

112121
// 避免除以零
113122
return time > 0 ? distance / time : 0;
114-
}, [maxSamples]);
123+
}, []);
115124

116125
/**
117126
* 处理滚动事件
118127
*
119128
* @param scrollOffset - 滚动偏移量
120129
*/
121130
const handleScroll = useCallback((scrollOffset: number): void => {
131+
const data = scrollDataRef.current;
132+
recordSample(scrollOffset);
133+
122134
// 设置滚动状态
123-
if (!isScrolling) {
135+
if (!data.isScrolling) {
136+
data.isScrolling = true;
124137
setIsScrolling(true);
125138
}
126139

127-
// 计算滚动速度
128-
const speed = calculateSpeed(scrollOffset);
129-
setScrollSpeed(speed);
140+
// rAF 合并状态更新,避免每次 scroll 事件触发 setState
141+
data.rafId ??= requestAnimationFrame(() => {
142+
data.rafId = null;
143+
const speed = calculateSpeed();
144+
if (Math.abs(speed - data.speed) >= speedEpsilon) {
145+
data.speed = speed;
146+
setScrollSpeed(speed);
147+
}
148+
});
130149

131150
// 清除之前的定时器
132-
if (scrollDataRef.current.timer !== null) {
133-
clearTimeout(scrollDataRef.current.timer);
151+
if (data.timer !== null) {
152+
clearTimeout(data.timer);
134153
}
135154

136155
// 设置新的定时器,检测滚动停止
137-
scrollDataRef.current.timer = setTimeout(() => {
156+
data.timer = setTimeout(() => {
157+
data.isScrolling = false;
158+
if (data.speed !== 0) {
159+
data.speed = 0;
160+
setScrollSpeed(0);
161+
}
138162
setIsScrolling(false);
139-
setScrollSpeed(0);
140163
// 清空样本数据
141-
scrollDataRef.current.positions = [];
164+
data.positions = [];
142165
}, scrollEndDelay);
143-
}, [isScrolling, calculateSpeed, scrollEndDelay]);
166+
}, [calculateSpeed, recordSample, scrollEndDelay, speedEpsilon]);
144167

145168
/**
146169
* 检查是否为快速滚动
@@ -151,15 +174,35 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
151174
* 重置滚动状态
152175
*/
153176
const reset = useCallback(() => {
177+
const data = scrollDataRef.current;
154178
setIsScrolling(false);
155179
setScrollSpeed(0);
156-
scrollDataRef.current.positions = [];
180+
data.isScrolling = false;
181+
data.speed = 0;
182+
data.positions = [];
157183

158-
if (scrollDataRef.current.timer !== null) {
159-
clearTimeout(scrollDataRef.current.timer);
160-
scrollDataRef.current.timer = null;
184+
if (data.timer !== null) {
185+
clearTimeout(data.timer);
186+
data.timer = null;
187+
}
188+
189+
if (data.rafId !== null) {
190+
cancelAnimationFrame(data.rafId);
191+
data.rafId = null;
161192
}
162193
}, []);
194+
195+
useEffect(() => {
196+
const data = scrollDataRef.current;
197+
return () => {
198+
if (data.timer !== null) {
199+
clearTimeout(data.timer);
200+
}
201+
if (data.rafId !== null) {
202+
cancelAnimationFrame(data.rafId);
203+
}
204+
};
205+
}, []);
163206

164207
return {
165208
/** 是否正在滚动 */

0 commit comments

Comments
 (0)