React Hooks 滚动事件回调中修改state不生效问题如何解决
问题根源
你的问题本质是React函数组件的闭包陷阱:
- 首次组件渲染时,
handle函数会捕获当前作用域下的初始myState值false - 你绑定滚动事件的
useEffect依赖只有props.run,只要props.run不变,就只会在首次符合条件时绑定一次初始的handle函数 - 后续
myState更新触发组件重渲染,生成的新handle函数会捕获最新的myState值,但你没有更新绑定的事件回调,元素上绑定的永远是最初那个闭包了旧myState的handle,所以每次判断!myState都为真,就会一直打印Set - 另外你单独写的空依赖卸载
useEffect也存在逻辑问题,它捕获的handle也是初始版本,卸载时可能无法正确移除事件。
修复方案
方案1:补全useEffect依赖,动态更新事件绑定
把事件绑定和卸载逻辑合并到同一个useEffect里,把handle加入依赖数组,这样每次handle变化时都会先移除旧事件,再绑定新事件,就能拿到最新的状态:
const [myState, setMyState] = useState(false); const refA = useRef(null) // 用useCallback缓存handle,避免不必要的事件重绑定 const handle = useCallback((e) => { if (!myState) { console.log('Set', myState) setMyState(true); }else { console.log('no set') } }, [myState]) useEffect(() => { if (!props.run || !refA.current) return; const ref = refA.current; ref.addEventListener("scroll", handle, { passive: true }); // 卸载逻辑直接写在同一个effect的返回里,自动清理旧事件 return () => { ref.removeEventListener("scroll", handle, { passive: true }); } }, [props.run, handle]); // 把handle加入依赖数组
方案2:用ref存储最新状态,避免频繁操作事件监听
如果不想频繁解绑/绑定事件,可以用一个ref同步myState的最新值,handle里直接读ref的current值,就不会受闭包影响:
const [myState, setMyState] = useState(false); const myStateRef = useRef(myState); // 同步状态到ref useEffect(() => { myStateRef.current = myState; }, [myState]); const refA = useRef(null) const handle = (e) => { if (!myStateRef.current) { // 读取ref而不是直接读state console.log('Set', myStateRef.current) setMyState(true); }else { console.log('no set') } } useEffect(() => { if (!props.run || !refA.current) return; const ref = refA.current; ref.addEventListener("scroll", handle, { passive: true }); return () => { ref.removeEventListener("scroll", handle, { passive: true }); } }, [props.run]); // 不需要加handle依赖,handle不会捕获旧状态
内容的提问来源于stack exchange,提问作者DeLe
相关产品推荐
相关产品推荐

