You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 04:25:20