Socket.io接收消息时useState状态未即时更新问题求助
问题原因
你碰到的是React的闭包陷阱:Socket.io的回调函数在组件首次渲染时就被绑定了,它捕获的是当时totalScore的初始值(0)。之后不管totalScore状态怎么更新,这个回调里的totalScore始终是闭包中保存的初始值,导致第二次触发时计算逻辑出错,看起来像是totalScore变回了0。
解决方案
下面提供三种可行的解决方式,你可以根据场景选择:
方案1:使用函数式更新获取最新状态
利用setState的函数式形式,直接获取前一次的最新状态,避免闭包带来的旧值问题:
socket.on(`${WsTopic.GAME}/${WsAction.STOP}`, (payload) => { const user = payload.find((entry: any) => entry.username == name) if (user) { console.log("USER: ", user," USER.SCORE: ", user.score); // 通过函数式更新拿到最新的prevTotalScore setTotalScore(prevTotalScore => { const addedScore = user.score - prevTotalScore; setAddedScore(addedScore); setShowAnswer(true); return user.score; }); } })
方案2:用useRef同步最新状态
用useRef保存totalScore的最新值,确保回调能随时获取到当前的状态:
首先声明ref并同步状态:
import { useState, useRef, useEffect } from 'react'; // ... const totalScoreRef = useRef<number>(0); const [totalScore, setTotalScore] = useState<number>(0); // 每次totalScore更新时同步到ref useEffect(() => { totalScoreRef.current = totalScore; }, [totalScore]);
然后修改回调逻辑:
socket.on(`${WsTopic.GAME}/${WsAction.STOP}`, (payload) => { const user = payload.find((entry: any) => entry.username == name) if (user) { console.log("USER: ", user," USER.SCORE: ", user.score); console.log("SCORE: ", totalScoreRef.current); const addedScore = user.score - totalScoreRef.current; setTotalScore(user.score); setAddedScore(addedScore); setShowAnswer(true); } })
方案3:用useCallback+useEffect重新绑定事件
如果组件中name等依赖项可能变化,用这种方式确保回调始终拿到最新的状态和依赖:
import { useState, useCallback, useEffect } from 'react'; // ... const [totalScore, setTotalScore] = useState<number>(0); const [addedScore, setAddedScore] = useState<number>(0); const [showAnswer, setShowAnswer] = useState<boolean>(false); const handleStopMessage = useCallback((payload) => { const user = payload.find((entry: any) => entry.username == name) if (user) { console.log("USER: ", user," USER.SCORE: ", user.score); console.log("SCORE: ", totalScore); const addedScore = user.score - totalScore; setTotalScore(user.score); setAddedScore(addedScore); setShowAnswer(true); } }, [name, totalScore]); // 添加所有依赖项 // 组件挂载/依赖变化时绑定事件,卸载时解绑 useEffect(() => { const eventName = `${WsTopic.GAME}/${WsAction.STOP}`; socket.on(eventName, handleStopMessage); return () => { socket.off(eventName, handleStopMessage); }; }, [handleStopMessage]);
内容的提问来源于stack exchange,提问作者Bog the Coder
相关产品推荐
相关产品推荐

