You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 09:15:02