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

React函数组件中如何获取更新后的state值?

React函数组件中无法获取更新后state值的问题

问题背景

在React函数组件中遇到无法获取更新后state值的问题,以下是相关代码示例及场景说明:

第一版代码

const TestApp = () => {
  const [speed, setSpeed] = useState(1);

  const incrementSpeed = () => {
    console.log(speed, `speed`); // 始终输出1,如何获取新的state?
    setSpeed(speed + 1);
    window.requestAnimationFrame(incrementSpeed);
  };

  useEffect(() => {
    window.addEventListener('keydown', (e) => {
      if (e.key === 'z') {
        incrementSpeed();
      }
    });
  }, []);
  return <>></>;
};

export default TestApp;

尝试现有方案后未解决问题,因此更新代码至第二版:

第二版代码

const TestApp = () => {
  const [speed, setSpeed] = useState(1);

  // 其他函数更新state后,该函数无法获取新state
  const incrementSpeed = () => {
    console.log(speed, `speed`); // 始终输出1,如何获取新的state?
    window.requestAnimationFrame(incrementSpeed);
  };

  useEffect(() => {
    const handle = (e) => {
      if (e.key === 'o') {
        setSpeed((speed) => speed + 1);
      } else if (e.key === 'p') {
        incrementSpeed();
      }
    };
    window.addEventListener('keydown', handle);
    return () => window.removeEventListener('keydown', handle);
  }, [speed]);
  return <>></>;
};

export default TestApp;

存在两个操作:

  • 按下'o'键将speed加1
  • 按下'p'键调用incrementSpeed函数

incrementSpeed仅触发一次,但setSpeed可多次触发,已知该函数拿旧state的原因,但不知如何修复。

实际场景(第三版)

上述为简化示例,实际是开发浏览器扩展,包含两个功能:

  • 自动滚动
  • 调整滚动速度

自动滚动函数autoRead代码如下:

const autoRead = () => {
  if (stopAutoReading.current) {
    window.requestAnimationFrame(autoRead);
    return;
  }
  recordScroll.current += config.speed / 100;
  document.documentElement.scrollTop = recordScroll.current;
  if (enableAutoReading.current) {
    window.requestAnimationFrame(autoRead);
  }
};

按下'x'键触发autoRead,按下左右箭头调整滚动速度,enableAutoReading为控制函数执行的ref,同样遇到无法获取更新后speed值的问题。


解决方案

核心原因

函数组件中,incrementSpeed和autoRead这类被requestAnimationFrame反复调用的函数,会捕获组件渲染时的state值形成闭包。后续state更新触发组件重新渲染,但旧的函数实例依然持有旧的state引用,导致无法获取最新值。

针对示例代码的修复方法

方法1:使用useRef保存最新state

通过useRef创建引用,在state更新时同步更新该ref,让闭包中的函数随时获取最新state值:

const TestApp = () => {
  const [speed, setSpeed] = useState(1);
  const speedRef = useRef(speed);

  // 同步state到ref
  useEffect(() => {
    speedRef.current = speed;
  }, [speed]);

  const incrementSpeed = () => {
    console.log(speedRef.current, `speed`); // 现在能获取最新值
    setSpeed(prev => prev + 1);
    window.requestAnimationFrame(incrementSpeed);
  };

  useEffect(() => {
    const handleKeydown = (e) => {
      if (e.key === 'z') {
        incrementSpeed();
      }
    };
    window.addEventListener('keydown', handleKeydown);
    return () => window.removeEventListener('keydown', handleKeydown);
  }, []);
  
  return <>></>;
};

方法2:使用useCallback配合依赖更新函数

用useCallback包裹函数并加入speed依赖,同时注意管理requestAnimationFrame的请求ID,避免重复执行:

const TestApp = () => {
  const [speed, setSpeed] = useState(1);
  const requestIdRef = useRef(null);

  const incrementSpeed = useCallback(() => {
    console.log(speed, `speed`);
    setSpeed(prev => prev + 1);
    requestIdRef.current = window.requestAnimationFrame(incrementSpeed);
  }, [speed]);

  useEffect(() => {
    const handleKeydown = (e) => {
      if (e.key === 'z') {
        // 先取消之前的请求,避免重复执行
        if (requestIdRef.current) {
          window.cancelAnimationFrame(requestIdRef.current);
        }
        incrementSpeed();
      }
    };
    window.addEventListener('keydown', handleKeydown);
    return () => {
      window.removeEventListener('keydown', handleKeydown);
      if (requestIdRef.current) {
        window.cancelAnimationFrame(requestIdRef.current);
      }
    };
  }, [incrementSpeed]);
  
  return <>></>;
};

针对实际浏览器扩展场景的修复

将config.speed替换为ref保存的最新值即可:

const TestApp = () => {
  const [speed, setSpeed] = useState(1);
  const speedRef = useRef(speed);
  const stopAutoReading = useRef(false);
  const recordScroll = useRef(0);
  const enableAutoReading = useRef(false);

  useEffect(() => {
    speedRef.current = speed;
  }, [speed]);

  const autoRead = () => {
    if (stopAutoReading.current) {
      window.requestAnimationFrame(autoRead);
      return;
    }
    // 使用ref中的最新speed值
    recordScroll.current += speedRef.current / 100;
    document.documentElement.scrollTop = recordScroll.current;
    if (enableAutoReading.current) {
      window.requestAnimationFrame(autoRead);
    }
  };

  useEffect(() => {
    const handleKeydown = (e) => {
      if (e.key === 'x') {
        enableAutoReading.current = true;
        autoRead();
      } else if (e.key === 'ArrowLeft') {
        setSpeed(prev => Math.max(1, prev - 1));
      } else if (e.key === 'ArrowRight') {
        setSpeed(prev => prev + 1);
      }
    };
    window.addEventListener('keydown', handleKeydown);
    return () => window.removeEventListener('keydown', handleKeydown);
  }, []);
  
  return <>></>;
};

内容的提问来源于stack exchange,提问作者Eve-Sama

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 16:53:20