React接收WebSocket消息时状态重置为默认值的问题求助
React WebSocket接收消息时状态重置为初始值问题解决
我正在开发一个使用WebSocket接收实时更新的React项目,但遇到一个问题:每次收到WebSocket消息时,组件状态都会重置为默认值。最初用useState钩子,之后改用useReducer,两种情况均出现该问题。已确认dispatch后状态会更新,但收到消息时状态又会回到初始值。
Reducer代码
const initialState = { name: "", message: "", word: "", color: "#000000", eraseMode: false, strokeWidth: 2, eraserWidth: 10, isDrawing: false, id: null, turnID: null, names: null, scores: null, msgArr: [], } function reducer(state, action) { switch (action.type) { case "setAll": return { ...state, id: action.payload.id, turnID: action.payload.turnID, names: action.payload.names, scores: action.payload.scores, } case "setName": return { ...state, name: action.payload } case "setMessage": return { ...state, message: action.payload } case "setWord": return { ...state, word: action.payload } case "setColor": return { ...state, color: action.payload } case "setErasemode": return { ...state, eraseMode: action.payload } case "setStrokeWidth": return { ...state, strokeWidth: action.payload } case "setEraserWidth": return { ...state, eraserWidth: action.payload } case "setIsDrawing": return { ...state, isDrawing: action.payload } case "setId": return { ...state, id: action.payload } case "setTurnID": return { ...state, turnID: action.payload } case "setNames": return { ...state, names: action.payload } case "setScores": return { ...state, scores: action.payload } case "addMsg": return { ...state, msgArr: [...state.msgArr, action.payload] } default: throw new Error() } }
函数组件代码
const [state, dispatch] = useReducer(reducer, initialState) const ws = useRef(null) const url = "ws://localhost:9833" const p = useParams() useEffect(() => { // Initialize WebSocket const connectWebSocket = () => { if (ws.current) { ws.current.onerror = ws.current.onopen = ws.current.onclose = null ws.current.close() } ws.current = new WebSocket(`${url}/${p.roomID}`) console.log("Attempting to connect to WebSocket:", `${url}/${p.roomID}`) ws.current.onopen = () => { console.log("WebSocket connection opened") } ws.current.onerror = (error) => { console.error("WebSocket error:", error) } ws.current.onclose = () => { console.log("WebSocket connection closed") // Attempt to reconnect in case of a connection failure toast.error("Connection lost!") ws.current = null dispatch({ type: "setId", payload: null }) setTimeout(() => connectWebSocket(), 1000) } ws.current.onmessage = (message) => { console.log("when WebSocket message received:", state) //here state is always equal to initial state const msg = JSON.parse(message.data) switch (msg.type) { case 0: if (msg.turnID === msg.id) { dispatch({ type: "setWord", payload: msg.word }) } dispatch({ type: "setAll", payload: { id: msg.id, turnID: msg.turnID, names: msg.names, scores: msg.scores, }, }) break case 1: break case 3: if (msg.isTrue) { dispatch({ type: "addMsg", payload: `G ${state.names[msg.id]} guessed the word!👏👏`, }) dispatch({ type: "setScores", payload: msg.scores }) } else { dispatch({ type: "addMsg", payload: `B ${state.names[msg.id]}: ${msg.message}`, }) } break case 4: dispatch({ type: "addMsg", payload: `S ${state.names[msg.turnID]} is drawing now.`, }) dispatch({ type: "setTurnID", payload: msg.turnID }) if (msg.turnID === state.id) dispatch({ type: "setWord", payload: msg.word }) break case 6: dispatch({ type: "addMsg", payload: `O ${msg.name} joined the room.`, }) dispatch({ type: "setScores", payload: msg.scores }) dispatch({ type: "setNames", payload: msg.names }) break case 7: dispatch({ type: "addMsg", payload: `R ${state.names[msg.id]} left the room.`, }) dispatch({ type: "setScores", payload: msg.scores }) dispatch({ type: "setNames", payload: msg.names }) break default: break } } } connectWebSocket() // Cleanup on component unmount return () => { if (ws.current) { ws.current.onerror = ws.current.onopen = ws.current.onclose = null ws.current.close() } } }, [p.roomID])
问题原因
问题出在闭包捕获上:useEffect仅在p.roomID变化时执行,此时创建的onmessage回调会捕获当前的state(也就是初始状态)。后续组件状态更新时,这个回调并不会重新创建,所以它始终访问的是初始的state值,导致你看到状态重置的假象。
修复方案
使用useRef保存最新的状态引用,每次状态更新时同步到ref中,这样onmessage回调就能获取到最新的状态:
步骤1:添加状态引用
在组件中新增一个ref来跟踪最新状态:
const [state, dispatch] = useReducer(reducer, initialState) const stateRef = useRef(state) // 保存最新状态的引用 // 每次state更新时同步到ref useEffect(() => { stateRef.current = state }, [state])
步骤2:修改onmessage回调
将回调中所有直接使用state的地方替换为stateRef.current:
ws.current.onmessage = (message) => { console.log("when WebSocket message received:", stateRef.current) const msg = JSON.parse(message.data) switch (msg.type) { // ...其他case保持不变 case 3: if (msg.isTrue) { dispatch({ type: "addMsg", payload: `G ${stateRef.current.names[msg.id]} guessed the word!👏👏`, }) dispatch({ type: "setScores", payload: msg.scores }) } else { dispatch({ type: "addMsg", payload: `B ${stateRef.current.names[msg.id]}: ${msg.message}`, }) } break case 4: dispatch({ type: "addMsg", payload: `S ${stateRef.current.names[msg.turnID]} is drawing now.`, }) dispatch({ type: "setTurnID", payload: msg.turnID }) if (msg.turnID === stateRef.current.id) dispatch({ type: "setWord", payload: msg.word }) break case 7: dispatch({ type: "addMsg", payload: `R ${stateRef.current.names[msg.id]} left the room.`, }) dispatch({ type: "setScores", payload: msg.scores }) dispatch({ type: "setNames", payload: msg.names }) break // ...其他case保持不变 } }
为什么这样有效?
useRef的current属性是可变的,不会受组件重新渲染的影响。我们通过useEffect在每次state更新时同步最新值到stateRef.current,这样onmessage回调就能随时访问到最新的组件状态,而不是被闭包捕获的初始值。
内容的提问来源于stack exchange,提问作者user25703354
相关产品推荐
相关产品推荐

