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
相关产品推荐
相关产品推荐

