React+Express滚动加载SoundCloud资源出现重复内容求助
问题排查与修复方案
核心问题根源
- React元素key使用错误:用
index作为key会导致数组更新时React复用旧组件,引发渲染错乱、内容重复。 - Ref数组内存泄漏:每次渲染都向
elementsRef中push新元素,导致数组积累大量重复DOM引用,IntersectionObserver重复监听触发多次加载。 - 无限滚动Observer重复创建:监听最后一个元素的
useEffect依赖usersData,每次数据更新都重建Observer,旧Observer未清理会触发重复请求。 - 后端缓存刷新导致分页内容重复:后端定时洗牌缓存数组,前端
offset持续累加,相同偏移量对应内容因数组顺序改变而重复。
分步修复
1. 替换React元素的唯一Key
将index替换为track的唯一ID(如data.track.id),确保React正确识别元素变化:
{usersData.map((data, index) => { const isLastItem = index === usersData.length - 1; return ( <div className="feed-player-container" data-user={data.user} ref={(element) => { elementsRef.current[index] = element; if (isLastItem) lastTrackRef.current = element; }} key={data.track.id} // 使用track唯一ID作为key > <ReactPlayer url={data.track.url} className="react-player" /> </div> ); })}
2. 修复Ref数组重复添加问题
直接通过索引赋值Ref,避免每次渲染都push重复元素:
ref={(element) => { elementsRef.current[index] = element; // 按索引覆盖,防止重复 if (isLastItem) lastTrackRef.current = element; }}
3. 优化无限滚动请求逻辑
添加加载状态防止重复请求,重构Observer依赖避免重复创建:
const [isLoading, setIsLoading] = useState(false); const fetchTracks = () => { if (isLoading) return; // 阻止重复请求 setIsLoading(true); fetch(`http://localhost:4000/api/soundcloud?offset=${offset}&limit=${limit}`) .then((response) => { if (!response.ok) throw new Error(`Request failed: ${response.status}`); return response.json(); }) .then((data) => { setUsersData(old => [...old, ...data.message]); setOffset(old => old + limit); }) .catch(console.error) .finally(() => setIsLoading(false)); }; // 重构最后元素监听逻辑 useEffect(() => { const observer = new IntersectionObserver( entries => { entries.forEach(entry => { if (entry.isIntersecting && !isLoading) fetchTracks(); }); }, { threshold: 0.5 } ); const currentLast = lastTrackRef.current; if (currentLast) observer.observe(currentLast); return () => { if (currentLast) observer.unobserve(currentLast); }; }, [lastTrackRef.current, isLoading]);
4. 解决后端缓存刷新导致的内容重复
后端添加缓存版本标识,前端检测版本变化时重置数据:
后端修改:
let dataCache = null; let cacheVersion = 0; // 新增缓存版本号 const fetchData = async () => { // ... 原有逻辑 ... shuffleArray(arr); dataCache = arr; cacheVersion += 1; // 缓存刷新时版本号+1 }; app.get("/api/soundcloud", (req, res) => { const offset = parseInt(req.query.offset) || 0; const limit = parseInt(req.query.limit) || 10; if (dataCache) { const paginatedData = dataCache.slice(offset, offset + limit); res.json({ message: paginatedData, version: cacheVersion // 返回当前版本 }); } else { res.status(500).json({ error: "No data available" }); } });
前端修改:
const [cacheVersion, setCacheVersion] = useState(null); const fetchTracks = () => { if (isLoading) return; setIsLoading(true); fetch(`http://localhost:4000/api/soundcloud?offset=${offset}&limit=${limit}`) .then(response => { if (!response.ok) throw new Error(`Request failed: ${response.status}`); return response.json(); }) .then(data => { if (!cacheVersion) { setCacheVersion(data.version); setUsersData(data.message); setOffset(limit); } else if (data.version !== cacheVersion) { // 缓存已刷新,重置数据 setUsersData(data.message); setOffset(limit); setCacheVersion(data.version); } else { setUsersData(old => [...old, ...data.message]); setOffset(old => old + limit); } }) .catch(console.error) .finally(() => setIsLoading(false)); };
5. 优化电台Logo监听逻辑
清理重复的Observer监听:
useEffect(() => { const observer = new IntersectionObserver( entries => { entries.forEach(entry => { if (entry.isIntersecting) { setCurrentRadioStation(entry.target.dataset.user); } }); }, { threshold: 0.5 } ); elementsRef.current.forEach(element => { if (element) observer.observe(element); }); return () => { elementsRef.current.forEach(element => { if (element) observer.unobserve(element); }); }; }, [usersData]);
内容的提问来源于stack exchange,提问作者heckspoiler
相关产品推荐
相关产品推荐

