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

React中handleScroll函数内State无法正常工作的原因是什么?

问题分析与解决方案

这是函数组件特有的闭包陷阱导致的:
你在首次渲染的useEffect(空依赖数组[])里绑定了handleScroll,这个函数会捕获当时的scrollPos、height、top——也就是它们的初始值(都是0)。后续组件重新渲染时,虽然state已经更新,但旧的handleScroll依然拿着初始值计算,所以滚动时用的都是第一次的数值。而页面<p>标签里的{height}能正常显示,是因为每次setHeight触发重新渲染,新的渲染周期里height是最新值,JSX会直接使用这个最新值。

类组件没有这个问题,是因为类组件的this.state是可变对象,每次访问this.state.scrollPos都是取最新的属性值,不存在闭包捕获旧值的情况。

下面是几种可行的解决方案:

方案1:用useRef保存最新state值

把需要在滚动回调里访问的state同步到ref中,这样回调里能直接拿到最新值:

const [scrollPos, setScrollPos] = useState(0);
const [height, setHeight] = useState(0);
const [top, setTop] = useState(0);
const ref = useRef(null);

// 用ref存储最新的state
const scrollPosRef = useRef(scrollPos);
const heightRef = useRef(height);
const topRef = useRef(top);

// 每次state更新时同步ref
useEffect(() => {
  scrollPosRef.current = scrollPos;
  heightRef.current = height;
  topRef.current = top;
}, [scrollPos, height, top]);

useEffect(() => {
  // 初始化height和scrollPos
  setHeight(ref?.current?.clientHeight);
  setScrollPos(-window.pageYOffset);

  const handleScroll = () => {
    const x = -window.pageYOffset;
    const dif = x - scrollPosRef.current;
    // 用最新的state值计算top
    const newTop = topRef.current + dif;
    setTop(newTop > 0 ? 0 : newTop < -heightRef.current ? -heightRef.current : newTop);
    setScrollPos(x);
  };

  window.addEventListener("scroll", handleScroll);
  // 组件卸载时清理监听器
  return () => window.removeEventListener("scroll", handleScroll);
}, []);

方案2:使用函数式更新获取最新state

React的setState支持传入函数,函数参数是当前最新的state值,这样可以避开闭包捕获旧值的问题:

const [scrollPos, setScrollPos] = useState(0);
const [height, setHeight] = useState(0);
const [top, setTop] = useState(0);
const ref = useRef(null);

useEffect(() => {
  setHeight(ref?.current?.clientHeight);
  setScrollPos(-window.pageYOffset);
  
  const handleScroll = () => {
    const x = -window.pageYOffset;
    // 用函数式更新获取最新的top
    setTop((prevTop) => {
      const dif = x - scrollPos;
      const newTop = prevTop + dif;
      return newTop > 0 ? 0 : newTop < -height ? -height : newTop;
    });
    setScrollPos(x);
  };

  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, [height, scrollPos]);

方案3:让useEffect依赖state,每次更新重新绑定监听器

把scrollPos、height、top加入useEffect的依赖数组,每次state变化时移除旧监听器、绑定新的handleScroll,这样新回调会捕获最新的state:

const [scrollPos, setScrollPos] = useState(0);
const [height, setHeight] = useState(0);
const [top, setTop] = useState(0);
const ref = useRef(null);

// 初始化height和scrollPos
useEffect(() => {
  setHeight(ref?.current?.clientHeight);
  setScrollPos(-window.pageYOffset);
}, []);

// 每次state变化时重新绑定滚动监听器
useEffect(() => {
  const handleScroll = () => {
    const x = -window.pageYOffset;
    const dif = x - scrollPos;
    setTop(top + dif > 0 ? 0 : top + dif < -height ? -height : top + dif);
    setScrollPos(x);
  };

  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, [scrollPos, height, top]);

内容的提问来源于stack exchange,提问作者donCiuarin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:18:04