React组件中socket事件处理器调用的state始终为初始值问题
问题原因与解决方案
问题根源
这是典型的React闭包陷阱:
你的useEffect依赖数组是空的,只会在组件首次挂载时执行一次。此时绑定的socket.on("newNotification")回调,捕获的是组件第一次渲染时生成的showCurrent函数——这个函数里的current是初始值0。
每次点击increment按钮,组件重新渲染时都会生成新的showCurrent函数(里面的current是最新值),但socket的回调还是使用第一次绑定的旧函数,所以触发事件时只会打印0。
而点击show按钮能拿到正确值,是因为按钮的onClick在每次渲染时都会绑定最新的showCurrent函数,调用的自然是包含最新current的版本。
解决方案
方案1:更新useEffect依赖,正确绑定/解绑回调
把showCurrent和socket加入依赖数组,每次依赖变化时先解绑旧回调,再绑定新的:
useEffect(() => { // 单独定义回调函数,方便解绑 const handleNewNotification = () => { showCurrent(); }; socket.on("newNotification", handleNewNotification); // 清理函数要解绑对应的回调 return () => { socket.off("newNotification", handleNewNotification); }; }, [showCurrent, socket]); // 加入依赖
这样每次showCurrent更新(组件重新渲染后),都会替换socket的回调,保证触发时调用的是最新版本的函数。
方案2:用useRef保存最新的current值
通过useRef同步current的最新值,让showCurrent始终能访问到最新状态:
import { useContext, useEffect, useState, useRef } from "react"; export const DashboardNavbar = (props) => { const socket = useContext(WebsocketContext); const [current, setCurrent] = useState(0); const currentRef = useRef(current); // 同步state到ref,确保ref始终指向最新值 useEffect(() => { currentRef.current = current; }, [current]); console.log("dashboard is rerendering...") const showCurrent = () => { // 从ref中取最新值 console.log("showCurrent is running and current = ", currentRef.current); }; const incrementCurent = () => { setCurrent((prev) => prev + 1); }; useEffect(() => { socket.on("newNotification", () => { showCurrent(); }); return () => { socket.off("newNotification"); }; }, [socket]); return ( <> <button onClick={showCurrent}>show</button> <button onClick={incrementCurent}>increment</button> </> ); } export default DashboardNavbar;
这个方案不需要修改原useEffect的依赖,适合不想频繁绑定/解绑socket回调的场景。
内容的提问来源于stack exchange,提问作者Ahmed Sbai
相关产品推荐
相关产品推荐

