React中用requestIdleCallback优化长列表还是卡顿怎么办?
我在用React实现一个动态加载的长列表,尝试用requestIdleCallback分批次渲染列表项,但滚动到后面还是会卡顿。我按网上的方法在useEffect里这样写的:
const [items, setItems] = useState([]);
useEffect(() => {
let offset = 0;
const renderBatch = () => {
requestIdleCallback(() => {
const batch = generateItems(50, offset);
setItems(prev => [...prev, ...batch]);
offset += 50;
return offset < totalItems ? renderBatch() : undefined;
});
};
renderBatch();
}, []);
但测试时发现:1. 滚动到第100项后开始明显卡顿 2. 滑动停止后界面有0.5秒延迟渲染。是不是requestIdleCallback的使用时机有问题?或者需要配合其他优化手段?
requestIdleCallback的回调优先级太低,等屏幕静止才渲染,但你滚动时它会积压任务,导致滑停后才突然加载,造成你说的0.5秒延迟。根本问题是:你不该等空闲才渲染,而应该在滚动时按帧切片渲染。React 的并发模式(v18+)已经支持时间切片,但你可以手动模拟。
我建议你改成
setTimeout+useRef控制渲染批次,这样更可控:这样每帧只渲染一小批,不会长时间阻塞主线程,滚动更顺。而且你不该一口气加载到尾,应该监听滚动事件,只加载当前视口附近的项,不然数据量一大照样卡。
建议加个虚拟滚动(如 react-window),只渲染可见区域的列表项,性能能提升一个档次。
requestIdleCallback本身不是万能的,它只是尽可能利用空闲时间做事情,不保证执行时间。React 的渲染本身是同步的,即使你分批次更新状态,每个批次还是会导致一次 re-render,当列表项过多时,组件树过深依然会卡顿。问题可能出在两个地方:一是 DOM 节点数量没控制,二是 setState 的频率和渲染压力没控制好。
解决方法可以试试这些:
使用
windowing技术(虚拟滚动),比如react-window或react-virtualized,只渲染可视区域附近的元素,大大减少 DOM 节点数量。如果你想继续用
requestIdleCallback,可以加上一个“优先级”判断,例如用户正在滚动时暂停加载,等滚动结束再恢复。可以通过requestIdleCallback的deadline参数判断当前帧是否有空闲时间。使用
useMemo或React.memo减少重复渲染,确保列表项的 props 不变时复用渲染结果。用
setTimeout做懒加载兜底,如果requestIdleCallback长时间没执行,可以 fallback 到定时器。这是个简化版思路: