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

React嵌套事件处理函数中state未更新问题求助

问题:wheel事件监听中无法获取更新后的state值

我遇到一个React状态更新的问题:调用setLocked(true)后,wheelEventListener里的locked始终输出false,无法获取更新后的状态。如果直接用ref代替state,又无法触发OtherComponent的重新渲染。目前只能同时维护state和ref来实现需求,想寻求更优雅的解决方案。

相关代码如下:

import { useState, useEffect, useRef } from 'react';

const OtherComponent = ({ locked }) => {
  return (
    <div style={{position: 'fixed'}}>
      {locked ? 'Locked' : 'Unlocked'}
    </div>
  );
}

const PageComponent = () => {
  const [locked, setLocked] = useState(false);
  const mainRef = useRef(null);

  useEffect(() => {
    document.addEventListener('scroll', scrollListener, {passive: true});
  }, [])

  const scrollListener = () => {
    document.removeEventListener('scroll', scrollListener);

    setLocked(true);

    mainRef.current.addEventListener('wheel', wheelEventListener, {passive: true});
  }

  const wheelEventListener = () => {
    // locked always === false here, expecting true
    console.log('locked: ', locked);
  }

  return (
    <div ref={mainRef} style={{height: "5000px"}}>
      <OtherComponent locked={locked} />
    </div>
  );
}

export default PageComponent;

问题原因

这是闭包陷阱导致的:

  • 组件首次渲染时,scrollListener被绑定到document的scroll事件,它内部引用的wheelEventListener是此时创建的函数,该函数捕获了初始状态下的locked(值为false)。
  • 当调用setLocked(true)触发组件重新渲染后,会生成新的wheelEventListener函数(能拿到更新后的locked),但此前绑定到mainRef.current上的仍是旧的wheelEventListener,所以它始终输出false。

优雅解决方案

方案1:用useRef同步state最新值

通过ref同步state的最新值,既保证OtherComponent能随state更新重新渲染,又能在事件监听中获取到最新状态,无需冗余维护两个变量:

import { useState, useEffect, useRef } from 'react';

const OtherComponent = ({ locked }) => {
  return (
    <div style={{position: 'fixed'}}>
      {locked ? 'Locked' : 'Unlocked'}
    </div>
  );
}

const PageComponent = () => {
  const [locked, setLocked] = useState(false);
  const lockedRef = useRef(locked); // 用于同步state最新值
  const mainRef = useRef(null);

  // 当locked变化时,同步到ref
  useEffect(() => {
    lockedRef.current = locked;
  }, [locked]);

  useEffect(() => {
    document.addEventListener('scroll', scrollListener, {passive: true});
    return () => {
      document.removeEventListener('scroll', scrollListener);
    };
  }, [])

  const scrollListener = () => {
    document.removeEventListener('scroll', scrollListener);
    setLocked(true);
    mainRef.current.addEventListener('wheel', wheelEventListener, {passive: true});
  }

  const wheelEventListener = () => {
    // 从ref获取最新的locked值
    console.log('locked: ', lockedRef.current);
  }

  return (
    <div ref={mainRef} style={{height: "5000px"}}>
      <OtherComponent locked={locked} />
    </div>
  );
}

export default PageComponent;

方案2:用useEffect管理wheel事件绑定

利用React的生命周期特性,通过useEffect的依赖项自动处理wheel事件的绑定与解绑,确保事件监听函数始终能拿到最新的state:

import { useState, useEffect, useRef } from 'react';

const OtherComponent = ({ locked }) => {
  return (
    <div style={{position: 'fixed'}}>
      {locked ? 'Locked' : 'Unlocked'}
    </div>
  );
}

const PageComponent = () => {
  const [locked, setLocked] = useState(false);
  const mainRef = useRef(null);
  const scrollListenerRef = useRef();

  useEffect(() => {
    const scrollListener = () => {
      document.removeEventListener('scroll', scrollListener);
      setLocked(true);
    };
    scrollListenerRef.current = scrollListener;
    document.addEventListener('scroll', scrollListener, {passive: true});
    // 组件卸载时清理scroll事件
    return () => {
      document.removeEventListener('scroll', scrollListenerRef.current);
    };
  }, [])

  // 当locked状态变化时,处理wheel事件的绑定/解绑
  useEffect(() => {
    if (!locked || !mainRef.current) return;

    const wheelEventListener = () => {
      console.log('locked: ', locked);
    };

    mainRef.current.addEventListener('wheel', wheelEventListener, {passive: true});
    // 依赖变化时清理wheel事件
    return () => {
      mainRef.current.removeEventListener('wheel', wheelEventListener);
    };
  }, [locked])

  return (
    <div ref={mainRef} style={{height: "5000px"}}>
      <OtherComponent locked={locked} />
    </div>
  );
}

export default PageComponent;

方案优势对比

  • 方案1:逻辑简单直接,适合需要在事件监听中频繁获取最新state的场景,不改变原有事件绑定流程。
  • 方案2:更贴合React的生命周期管理理念,通过依赖项自动处理事件的注册与销毁,从根源避免闭包问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:23:14