react实现滚动加载
实现滚动加载的基本原理
滚动加载(Infinite Scroll)通过监听滚动事件,在用户接近页面底部时触发数据加载。React中通常结合useEffect和useRef实现,核心逻辑包括计算滚动位置、判断触底条件、异步获取数据。
监听滚动事件
在React函数组件中,使用useEffect绑定滚动事件监听器。组件卸载时需要移除监听以避免内存泄漏。
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
判断滚动触底条件
通过比较文档总高度、可视区域高度和已滚动距离,判断是否到达页面底部。通常设置一个阈值(如100px)提前触发加载。
const handleScroll = () => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadMoreData();
}
};
数据加载与状态管理
使用useState管理数据列表和加载状态,避免重复请求。加载完成后更新列表并重置加载状态。
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const loadMoreData = async () => {
if (loading) return;
setLoading(true);
const newData = await fetchData();
setItems(prev => [...prev, ...newData]);
setLoading(false);
};
优化性能与防抖
高频滚动事件可能导致性能问题,通过debounce或throttle限制触发频率。使用useCallback缓存函数避免重复创建。
const debouncedHandleScroll = useCallback(debounce(handleScroll, 300), []);
使用Intersection Observer替代滚动监听
现代浏览器支持Intersection Observer API,性能优于传统滚动事件。创建一个观察器监控底部占位元素。
const observerRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) loadMoreData();
});
if (observerRef.current) observer.observe(observerRef.current);
return () => observer.disconnect();
}, []);
完整代码示例
import React, { useState, useEffect, useRef, useCallback } from 'react';
import debounce from 'lodash.debounce';
function InfiniteScrollList() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const observerRef = useRef();
const fetchData = async () => {
// 模拟API请求
return new Array(10).fill(0).map((_, i) => ({ id: items.length + i }));
};
const loadMoreData = async () => {
if (loading) return;
setLoading(true);
const newData = await fetchData();
setItems(prev => [...prev, ...newData]);
setLoading(false);
};
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) loadMoreData();
},
{ threshold: 0.1 }
);
if (observerRef.current) observer.observe(observerRef.current);
return () => observer.disconnect();
}, [loading]);
return (
<div>
{items.map(item => (
<div key={item.id}>Item {item.id}</div>
))}
<div ref={observerRef}>
{loading && <p>Loading more items...</p>}
</div>
</div>
);
}
注意事项
- 需要处理API分页逻辑,确保每次请求获取新数据
- 列表项应设置唯一的
key属性以提高渲染性能 - 移动端需考虑触控滚动事件的兼容性
- 加载失败时需要提供重试机制
- 数据全部加载完成后应移除观察器或显示提示信息







