React项目执行WebSocket.send()时连接意外断开无法发消息问题求解
问题根因分析
- WebSocket实例重复创建:你把
ws = new WebSocket(...)直接写在了组件函数的顶层,组件每触发一次重渲染(比如state更新、props变化)都会执行这段代码,生成新的WebSocket实例,旧的实例会被覆盖且自动断开,这是调用send后连接突然断开的核心原因。 - useEffect清理逻辑错误:你在useEffect的返回函数里给
ws.close赋值了一个空函数,完全没执行关闭旧连接的逻辑,旧连接的残留事件监听器还会触发,进一步导致连接混乱。 - 异步setState取值错误:
imageUpload方法里调用setImage后立刻打印image,此时state还没更新,你传给convertBase64的还是旧值,可能导致base64生成异常,如果后续要发这个内容也会出错。 - 事件监听器重复绑定:你既在组件顶层绑定了
ws.onmessage、ws.onclose等事件,又在useEffect里重复绑定,逻辑冲突,会出现意料之外的执行结果。 - 后端适配问题:你当前发送的是纯文本
"data",如果Django后端的WebSocket consumer默认按JSON格式解析消息,解析失败会主动断开连接。
修复方案
核心代码修改
把WebSocket实例的创建和清理放到useEffect里,用useRef存储实例避免重渲染时被重置:
import { useState, useEffect, useRef } from 'react'; const PushNotifications = () => { const [show, setShow] = useState(true); const [image, setImage] = useState(null); const [title, setTitle] = useState(""); const [message, setMessage] = useState(""); const [base64Image, setBase64Image] = useState(""); // 用useRef存储ws实例,保证重渲染时实例不变 const wsRef = useRef(null); const convertBase64 = (file) => { return new Promise((resolve, reject) => { if (file) { const fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.onload = () => { resolve(fileReader.result); }; fileReader.onerror = (error) => { reject(error); }; } else { reject(new Error("无有效文件")) } }); }; const imageUpload = async (e) => { const file = e.target.files[0]; setImage(file); let base64 = await convertBase64(file); setBase64Image(base64); }; const sendNotification = (e) => { e.preventDefault(); const ws = wsRef.current; if (!ws) return; // 按Django后端要求格式发送,默认适配JSON格式 const data = JSON.stringify({ image: base64Image, title: title, message: message }); if (ws.readyState === WebSocket.OPEN) { ws.send(data); console.log("Sending notification"); } else if (ws.readyState === WebSocket.CONNECTING) { const sendOnOpen = () => { ws.send(data); ws.removeEventListener("open", sendOnOpen); } ws.addEventListener("open", sendOnOpen); console.log("Connecting state, 等待连接建立后发送"); } else { console.log("连接已关闭或异常,无法发送"); } }; // 所有ws相关逻辑放到useEffect里,只在组件挂载时执行一次 useEffect(() => { if (wsRef.current) return; const ws = new WebSocket("ws://192.168.18.112:8000/ws/notification/"); wsRef.current = ws; ws.onopen = (e) => { console.log("connect"); }; ws.onmessage = (e) => { let messageInfo = JSON.parse(e.data); console.log(messageInfo, "msg"); }; ws.onclose = (e) => { console.log("disconnect:", e); wsRef.current = null; }; ws.onerror = (e) => { console.log("ws error:", e); }; // 组件卸载时主动关闭连接 return () => { if (wsRef.current) { wsRef.current.close(); wsRef.current = null; } }; }, []); // 空依赖数组,仅挂载时执行一次 return ( <div> {/* 原有UI逻辑 */} </div> ) }
后端排查方向
如果修改前端代码后仍然出现发送即断开的情况,检查Django侧配置:
- 确认consumer的消息接收逻辑是否和前端发送的格式匹配
- 检查settings.py中CHANNEL_LAYERS、跨域CORS的配置是否正确
- 查看Django运行日志,确认调用send时后端是否有报错信息
内容的提问来源于stack exchange,提问作者hania
相关产品推荐
相关产品推荐

