React 分装webSocket

背景 AI 实时对话 需要流式数据

React Hooks 写法。新建WebSocket.tsx   放在根目录components

import { useCallback, useRef, useState } from 'react';

type MessageHandler = (message: MessageEvent) => void;
type ErrorHandler = (event: Event) => void;

export function useWebSocket(
  url: string,
  onMessage: MessageHandler,
  onError?: ErrorHandler,
  token?: string, // 新增 token 参数
) {
  const [isConnected, setIsConnected] = useState<boolean>(false);
  const [reconnectAttempts, setReconnectAttempts] = useState<number>(0);
  const [messages, setMessages] = useState<string[]>([]); // 用于存储消息
  const websocketRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<number | null>(null);
  const isManualClose = useRef<boolean>(false); // 改用 useRef 防止异步更新问题

  // 最大重连次数
  const maxReconnectAttempts = 5;
  // 初始重连延迟时间(毫秒)
  const initialReconnectDelay = 1000;

  // 连接 WebSocket
  const connectWebSocket = useCallback(() => {
    isManualClose.current = false; // 每次连接时重置手动关闭状态

    // 如果有 token,则通过 URL 查询参数传递
    const wsUrl = token ? `${url}?token=${token}` : url;

    websocketRef.current = new WebSocket(wsUrl);

    websocketRef.current.onopen = () => {
      console.log('WebSocket连接成功');
      setIsConnected(true);
      setReconnectAttempts(0); // 连接成功后重置重连次数
    };

    websocketRef.current.onmessage = (event: MessageEvent) => {
      onMessage(event);
      setMessages((prevMessages) => [...prevMessages, event.data]); // 将消息保存到状态
    };

    websocketRef.current.onerror = (event: Event) => {
      console.error('WebSocket发生错误:', event);
      if (onError) onError(event); // 如果提供了 onError 回调,调用它
    };

    websocketRef.current.onclose = () => {
      console.log('WebSocket连接关闭');
      setIsConnected(false);

      // 判断是否需要重连,只有在非手动断开时才尝试重连
      if (isManualClose.current) {
        console.log('WebSocket手动关闭,不进行重连');
        return; // 终止重连逻辑
      }

      if (reconnectAttempts < maxReconnectAttempts) {
        const delay = initialReconnectDelay * Math.pow(2, reconnectAttempts); // 指数级递增延迟
        console.log(`Reconnecting in ${delay / 1000} seconds...`);
        reconnectTimeoutRef.current = window.setTimeout(() => {
          setReconnectAttempts((prev) => prev + 1);
          connectWebSocket(); // 重试连接
        }, delay);
      } else {
        console.error('WebSocket重连失败次数过多,停止重连');
      }
    };
  }, [url, token, onMessage, onError, reconnectAttempts]);

  // 手动断开 WebSocket 连接
  const disconnectWebSocket = useCallback(() => {
    if (websocketRef.current) {
      console.log('手动断开 WebSocket 连接');
      isManualClose.current = true; // 使用 ref 直接设置为手动关闭
      websocketRef.current.close();
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current); // 清除任何重连的定时器
      }
    }
  }, []);

  // 发送消息
  const sendMessage = useCallback((message: string) => {
    if (
      websocketRef.current &&
      websocketRef.current.readyState === WebSocket.OPEN
    ) {
      websocketRef.current.send(message);
    } else {
      console.warn('WebSocket is not open.');
    }
  }, []);

  return {
    isConnected, // 是否已连接
    sendMessage, // 发送消息的函数
    disconnectWebSocket, // 手动断开连接的函数
    connectWebSocket, // 手动连接函数
    messages, // 返回存储的消息
  };
}

使用

import { useWebSocket } from '@/components/WebSocket';  //自己的文件路径
import { useEffect, useState } from 'react';
const AiChat = () => {
  const [isDialogue, setIsDialogue] = useState<boolean>(true);


  const {
    isConnected,
    sendMessage,
    disconnectWebSocket,
    connectWebSocket,
    messages,
  } = useWebSocket('wss://service-guide-uat.yadea.com.cn/ws', (message) => {
    console.log('Received message:', message.data);
  });
  const handleConnect = () => {
    console.log('手动触发连接');
    connectWebSocket(); // 手动触发连接
  };
  useEffect(() => {
    connectWebSocket();

    return () => {
      console.log('手动触发关闭');
      disconnectWebSocket();
    };
  }, []);

  return (
    <div className={style.box}>
     测试
    </div>
  );
};

export default AiChat;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/901836.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习(一)基础:神经网络、训练过程与激活函数(1/10)

深度学习基础&#xff1a;神经网络、训练过程与激活函数 引言&#xff1a; 深度学习作为机器学习的一个子领域&#xff0c;近年来在人工智能的发展中扮演了举足轻重的角色。它通过模仿人脑的神经网络结构&#xff0c;使得计算机能够从数据中学习复杂的模式和特征&#xff0c;…

List、Set、数据结构、Collections

一、数据结构 1.1 常用的数据结构 栈 栈&#xff1a;stack,又称堆栈&#xff0c;它是运算受限的线性表&#xff0c;其限制是仅允许在标的一端进行插入和删除操作&#xff0c;不允许在其他任何位置进行添加、查找、删除等操作。 简单的说&#xff1a;采用该结构的集合&#…

【网络协议栈】Tcp协议(下)的可靠性和高效性(超时重传、快速重传、拥塞控制、流量控制)

绪论: 承接上文&#xff0c;上文写到Tcp协议的结构以及对tcp协议的性能优化的滑动窗口&#xff0c;本章我们将继续了解Tcp协议的可靠性和高效性的具体展示。后面我将继续完善网络协议栈的网络层协议敬请期待&#xff01; 话不多说安全带系好&#xff0c;发车啦&#xff08;建议…

【AI绘画】Midjourney进阶:对角线构图详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;什么是构图为什么Midjourney要使用构图 &#x1f4af;对角线构图特点应用场景提示词书写技巧测试 &#x1f4af;小结 &#x1f4af;前言 【AI绘画】Midjourney进阶&a…

Linux常用命令1

切换目录 cd [rootlocalhost menge]# cd /[rootlocalhost /]# cd: cd [-L|[-P [-e]] [-]] [目录] 查看当前的目录 pwd 浏览目录内容 ls ls浏览后颜色表示 白色&#xff1a;普通文件 蓝色&#xff1a;目录 红色&#xff1a;压缩包文件 黄色&#xff1a;设备文件 绿…

以 6502 为例讲讲怎么阅读 CPU 电路图

开篇 你是否曾对 CPU 的工作原理充满好奇&#xff0c;以及简单的晶体管又是如何组成逻辑门&#xff0c;进而构建出复杂的逻辑电路实现&#xff1f;本文将以知名的 6502 CPU 的电路图为例&#xff0c;介绍如何阅读 CPU 电路图&#xff0c;并向你演示如何从晶体管电路还原出逻辑…

.NET Core WebApi第2讲:前后端分离,Restful

动态页面&#xff1a;数据流动 / Web服务器 / Ajax / 前后端分离 / restful风格源栈课堂一起帮https://17bang.ren/Code/261 一、Ajax&#xff1a;页面可以局部刷新 1、PPT演示:使用Ajax也无法减小带宽耗用 请求第一个页面&#xff0c;用AJAX从服务器端加载了一个页头。 请求第…

Maven进阶——坐标、依赖、仓库

目录 1.pomxml文件 2. 坐标 2.1 坐标的概念 2.2 坐标的意义 2.3 坐标的含义 2.4 自己项目的坐标 2.5 第三方项目坐标 3. 依赖 3.1 依赖的意义 3.2 依赖的使用 3.3 第三方依赖的查找方法 3.4 依赖范围 3.5 依赖传递和可选依赖 3.5.1 依赖传递 3.5.2 依赖范围对传…

ollama 在 Linux 环境的安装

ollama 在 Linux 环境的安装 介绍 他的存在在我看来跟 docker 的很是相似&#xff0c;他把市面上已经存在的大语言模型集合在一个仓库中&#xff0c;然后通过 ollama 的方式来管理这些大语言模型 下载 # 可以直接通过 http 的方式吧对应的 shell 脚本下载下来&#xff0c;然…

【10天速通Navigation2】(三) :Cartographer建图算法配置:从仿真到实车,从原理到实现

前言 往期内容&#xff1a; 第一期&#xff1a;【10天速通Navigation2】(一) 框架总览和概念解释第二期&#xff1a;【10天速通Navigation2】(二) &#xff1a;ROS2gazebo阿克曼小车模型搭建-gazebo_ackermann_drive等插件的配置和说明 本教材将贯穿nav2的全部内容&#xff0c…

【Android】Kotlin教程(4)

文章目录 1.field2.计算属性3.主构造函数4.次构造函数5.默认参数6.初始化块7.初始化顺序7.延迟初始化lateinit8.惰性初始化 1.field field 关键字通常与属性的自定义 getter 和 setter 一起使用。当你需要为一个属性提供自定义的行为时&#xff0c;可以使用 field 来访问或设置…

Visual Studio2022 Profile 工具使用

本篇研究下Visual Studio自带的性能分析工具&#xff0c;针对C代码&#xff0c;基于Visual Studio2022 文章目录 CPU使用率检测并发可视化工具使用率视图线程视图内核视图并发可视化工具SDK 参考资料 CPU使用率 对于CPU密集型程序&#xff0c;我们可以通过分析程序的CPU使用率…

【MySQL】MySQL数据库中密码加密和查询的解决方案

本篇博客是为了记录自己在遇到password函数无法生效时的解决方案。通过使用AES_ENCRYPT(str,key)和AES_DECRYPT(str,key)进行加密和解密。 一、问题 自己想创建一个user表&#xff0c;user表中有一个password属性列&#xff0c;自己想对密码进行加密后再存入数据库&#xff0c…

基于JAVA+SpringBoot+Vue的华府便利店信息管理系统

基于JAVASpringBootVue的华府便利店信息管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f3…

GCC 简介

Linux 中的编译器 GCC 的编译原理和使用详解 GCC 简介 GCC&#xff08;GNU Compiler Collection&#xff09;是一套由 GNU 开发的编程语言编译器&#xff0c;它支持多种编程语言&#xff0c;包括 C、C、Objective-C、Fortran、Ada 和 Go 等。GCC 是一个开源的工具集&#xff…

STM32F103C8T6 IO 操作

1.开启相关时钟 在 STM32 微控制器中&#xff0c;开启 GPIO 端口的时钟是确保 IO 口可以正常工作的第一步。 查找 RCC 寄存器使能时钟 在 STM32 中&#xff0c;时钟控制的寄存器通常位于 RCC (Reset and Clock Control) 模块中。不同的 STM32 系列&#xff08;如 STM32F1、STM…

vue3+vite 部署npm 包

公司需要所以研究了一下怎么部署安装&#xff0c;比较简单 先下载个vue项目 不用安准路由&#xff0c;pinna 啥的&#xff0c;只需要一个最简单的模版 删掉App.vue 中的其它组件 npm create vuelatest 开始写自定义组件 新建一个el-text 组件, name是重点&#xff0c;vue3中…

《Python游戏编程入门》注-第3章3

《Python游戏编程入门》的“3.2.4 Mad Lib”中介绍了一个名为“Mad Lib”游戏的编写方法。 1 游戏玩法 “Mad Lib”游戏由玩家根据提示输入一些信息&#xff0c;例如男人姓名、女人姓名、喜欢的食物以及太空船的名字等。游戏根据玩家输入的信息编写出一个故事&#xff0c;如图…

洛谷 P1226:【模板】快速幂

【题目来源】https://www.luogu.com.cn/problem/P1226【题目描述】 给你三个整数 a&#xff0c;b&#xff0c;p&#xff0c;求 a^b mod p。【输入格式】 输入只有一行三个整数&#xff0c;分别代表 a&#xff0c;b&#xff0c;p。【输出格式】 输出一行一个字符串 a^b mod ps&a…

Centos7快速重置root密码

1、重新启动Centos7&#xff0c;5秒内按向下方向键&#xff0c;使其停留在开机界面&#xff0c;如下图。 2、按’e’键&#xff0c;进入如下界面&#xff0c;移动向下方向键至“linux16”开头的行。然后按向右的方向键移动,找到“ro”并将其修改为“rw init/sysroot/bin/bash…