React中每秒更新useState时滚动元素位置异常的解决办法
问题描述
我有一个React组件,在useEffect里定义了每秒调用一次的函数,用来从API获取新数据。代码如下:
useEffect(() => { const getOverviewData = async () => { const endpoint1 = 'http://localhost:1111/overview/data/' const endpoint2 = 'http://localhost:1111/overview/test/' try { const responses = await Promise.all([ fetch(endpoint1), fetch(endpoint2), ]); const results = await Promise.all( responses.map(response => response.json()) ); setOverview(state => ({...state, data: results[0] ?? 0})) setOverview(state => ({...state, test: results[1] ?? 0})) // ... set other state variables setOverviewHistorical(prevData => ({ ...prevData, data: [...prevData.data, [Date.now(), results[0]]], test: [...prevData.test, [Date.now(), results[6] !== null ?results[6].toFixed(2) : 0]], })); } catch (error) { console.error('Error fetching data:', error); // Handle errors, maybe set some error state } } getOverviewData(); const interval = setInterval(() => { getOverviewData(); }, 1000); // 原注释标注1分钟,实际代码为1秒 return () => { clearInterval(interval) }; },[]) return (<Drawer PaperProps={{ sx: { width: "auto", backgroundColor: 'black,', height: '90%' }, ref: scrollRef, }} anchor={anchor} open={state[anchor]} onClose={(e) => toggleDrawer(false, e)} > element with scroll </Drawer>)
现在遇到的问题是:组件内有个设置了overflow-y: auto的滚动元素,用鼠标滚轮滚动时,滚动位置经常自动回退,无法停留在停止的位置。我猜测是每秒更新状态导致组件重渲染引发的,该如何解决?
解决方案
1. 减少不必要的重渲染
- 抽离滚动组件并缓存:把滚动内容封装成独立子组件,用
React.memo包裹,只有当props变化时才重渲染,避免父组件每秒更新时牵连它:const ScrollableContent = React.memo(({ children }) => ( <div style={{ overflowY: 'auto', height: '100%' }}> {children} </div> )); - 跳过无意义的状态更新:更新状态前对比新旧数据,只有数据真的变化时才调用setState,减少重触发:
setOverview(prev => { const newData = results[0] ?? 0; const newTest = results[1] ?? 0; if (prev.data === newData && prev.test === newTest) return prev; return {...prev, data: newData, test: newTest}; });
2. 手动保存并恢复滚动位置
如果无法避免重渲染,用useRef记录滚动位置,状态更新后强制恢复:
const scrollRef = useRef(null); const scrollPosition = useRef(0); // 监听滚动,保存位置 useEffect(() => { const container = scrollRef.current; if (!container) return; const savePosition = () => { scrollPosition.current = container.scrollTop; }; container.addEventListener('scroll', savePosition); return () => container.removeEventListener('scroll', savePosition); }, []); // 状态更新后恢复位置 useEffect(() => { const container = scrollRef.current; if (container) { container.scrollTop = scrollPosition.current; } }, [overview, overviewHistorical]);
注意将scrollRef绑定到实际滚动的容器元素上,而非Drawer的Paper(除非Paper是滚动容器)。
3. 优化历史数据更新
当前每次更新overviewHistorical都会创建新数组,数据量大时会加重渲染负担:
- 限制历史数据长度,比如只保留最近100条:
setOverviewHistorical(prev => ({ ...prev, data: [...prev.data.slice(-99), [Date.now(), results[0]]], test: [...prev.test.slice(-99), [Date.now(), results[6]?.toFixed(2) ?? 0]], })); - 用
useImmer简化状态更新,避免手动展开对象/数组,提升性能:import { useImmer } from 'use-immer'; const [overviewHistorical, setOverviewHistorical] = useImmer({ data: [], test: [] }); // 更新时直接修改草稿 setOverviewHistorical(draft => { draft.data.push([Date.now(), results[0]]); draft.test.push([Date.now(), results[6]?.toFixed(2) ?? 0]); // 限制长度 if (draft.data.length > 100) draft.data.shift(); if (draft.test.length > 100) draft.test.shift(); });
4. 修复Drawer的样式错误
代码中backgroundColor: 'black,缺少闭合引号,会导致样式解析异常,可能间接引发渲染问题,修正为:
sx: { width: "auto", backgroundColor: 'black', height: '90%' }
内容的提问来源于stack exchange,提问作者F9oOol_11
相关产品推荐
相关产品推荐

