React中UseContext仅返回初始值null,WebSocket上下文获取异常
React Context传递WebSocket实例:组件无法获取更新后实例的解决办法
问题背景
通过Context在深层组件访问WebSocket时,组件初始只能拿到null,WebSocket创建完成后组件也不会重新获取实例,加了加载状态后页面一直停留在加载页。控制台打印顺序显示:ControlScreen先输出socket null,之后上下文文件才输出已创建的socket,说明Socket创建时机晚于组件首次渲染,但Context未通知组件更新。
核心问题
当前实现用useRef存储WebSocket实例,但useRef的current属性变更不会触发组件重渲染。Context的value直接绑定ws.current,当ws.current更新时,React检测不到Provider的value变化(因为useRef的引用本身没变),所以子组件不会重新读取Context的新值。
修复方案
改用useState管理WebSocket实例,useState的更新会触发Provider组件重渲染,进而更新Context的value,通知所有消费组件:
1. 修改WebSocket上下文文件
const { createContext, useState, useEffect } = require('react'); const WS_URL = 'ws://localhost:3001'; const WebSocketContext = createContext({ socket: null }); function WebSocketProvider({ children }) { const computer = new URLSearchParams(document.location.search).get('computer'); const [socket, setSocket] = useState(null); useEffect(() => { const ws = new WebSocket(`${WS_URL}/?computer=${computer}`); ws.onopen = () => { if (ws.readyState === 1) { ws.send(JSON.stringify({ computer })); } setSocket(ws); // 连接成功后更新state,触发Provider重渲染 }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; return () => { ws.close(); }; }, [computer]); return ( <WebSocketContext.Provider value={{ socket }}> {children} </WebSocketContext.Provider> ); }; export { WebSocketProvider, WebSocketContext };
2. 优化ControlScreen组件
添加加载状态,并修正消息监听的清理逻辑:
import React, { useContext, useEffect, useState } from "react"; import { WebSocketContext } from "./WebsocketPage"; import { handleControlMessages } from "./HandleMessages"; import CreateScreens from "./CreateScreens"; function ControlScreen() { const [consList, setConsList] = useState([]); const { socket } = useContext(WebSocketContext); useEffect(() => { if (!socket) return; const handleMessage = (e) => { const message = JSON.parse(e.data); handleControlMessages(message, setConsList); }; socket.addEventListener('message', handleMessage); return () => { socket.removeEventListener('message', handleMessage); // 移除指定回调,避免内存泄漏 }; }, [socket]); // 未获取到socket时显示加载状态 if (!socket) { return <div>Loading...</div>; } return ( <div id="controlScreen"> <h1>Control Screen</h1> <CreateScreens consList={consList} /> </div> ); } export default ControlScreen;
额外优化点
- 移除了原代码中
computer的useRef,因为查询参数是组件初始化时的固定值,直接获取即可,无需用useRef存储。 - 原清理函数中
socket.removeEventListener('message')用法错误,必须传入对应的回调函数才能正确移除监听,否则会导致内存泄漏。
内容的提问来源于stack exchange,提问作者Agnes
相关产品推荐
相关产品推荐

