自定义websoket的reacthook(心跳检测,失败重连)

266 阅读2分钟
  1. 初始化所需的数据
    const ws = useRef<WebSocket | null>(null)
    // socket 数据
    const [wsData, setMessage] = useState({})
      // 锁定重连
    let lockReconnect = false;
      // 重连间隔
    let tt: Timeout | null = null;
    //  socket 状态
    const [readyState, setReadyState] = useState<any>({ key: 0, value:  '正在连接中'})
    //是否开发环境
    const isDev = process.env.DEPLOY_ENV === 'dev';
    //url地址
    const url = isDev ? `ws://${param.host}:${param.port}${param.url}`
                : 'ws://' + location.hostname + `:${param.port}${param.url}`;
  1. 创建websoket方法
    const creatWebSocket = () => {
            // 定义连接状态
            const stateArr = [{key: 0, value:  '正在连接中'},{key: 1, value: '已经连接并且可以通讯'},{key: 2, value:  '连接正在关闭'},{key: 3, value:  '连接已关闭或者没有连接成功'},]
            try{         
                ws.current =new WebSocket(url)
                ws.current.onopen = () => {setReadyState(stateArr[ws.current?.readyState ?? 0])}
                ws.current.onclose = () => {setReadyState(stateArr[ws.current?.readyState ?? 0])}
                ws.current.onerror = () => {setReadyState(stateArr[ws.current?.readyState ?? 0])}
                ws.current.onmessage = (e) => {setMessage({...JSON.parse(e.data)})}
                }  
            catch(error) {console.log(error)}}

3.定义方法

    //初始化
    const webSocketInit = () => {
                    if(!ws.current || ws.current.readyState === 3) {creatWebSocket()
    }}
    //  关闭 WebSocket
    const closeWebSocket = () => {ws.current?.close()}
    // 发送数据
    const sendMessage = (str:string) => {ws.current?.send(str)}
    
    //重连
    const reconnect = () => {
                try{
                    closeWebSocket()
                    ws.current =  null
                    creatWebSocket()
                    } 
                catch(e) {console.log(e)}

4.监听ws

    useEffect(() => {
            webSocketInit()
            return() => {ws.current?.close()
        }}, [ws]`

5.根据需要交出所需要的数据或方法

return{
        wsData,
        readyState,
        closeWebSocket,
        reconnect,
        sendMessage,
       }

完整代码

  interface urlConfig {
          host: string;
          port: string;
          url: string;
   }

  import { useState, useRef, useEffect } from 'react';
      const useWebsocket = (param: urlConfig) => {
        const ws = useRef<WebSocket | null>(null);
      // socket 数据
      const [wsData, setMessage] = useState({});
      //  socket 状态
      const [readyState, setReadyState] = useState<any>({ key: 0, value: '正在连接中' });
      const isDev = process.env.DEPLOY_ENV === 'dev';
      const url = isDev
        ? `ws://${param.host}:${param.port}${param.url}`
        : 'ws://' + location.hostname + `:${param.port}${param.url}`;
      const creatWebSocket = () => {
                const stateArr = [
                  { key: 0, value: '正在连接中' },
                  { key: 1, value: '已经连接并且可以通讯' },
                  { key: 2, value: '连接正在关闭' },
                  { key: 3, value: '连接已关闭或者没有连接成功' },
                ];

                try {
                  ws.current = new WebSocket(url);
                  ws.current.onopen = () => {
                    ws.current?.send('heartbeat');
                    setReadyState(stateArr[ws.current?.readyState ?? 0]);
                  };
                  ws.current.onclose = () => {
                    setReadyState(stateArr[ws.current?.readyState ?? 0]);
                  };
                  ws.current.onerror = () => {
                    setReadyState(stateArr[ws.current?.readyState ?? 0]);
                  };
                  ws.current.onmessage = (e) => {
                    setMessage({ ...JSON.parse(e.data) });
                  };
                } catch (error) {
                  console.log(error);
                }
      };
      const webSocketInit = () => {
        if (!ws.current || ws.current.readyState === 3) {
          creatWebSocket();
        }
      };
      const closeWebSocket = () => {
        ws.current?.close();
      };
      const sendMessage = (str: string) => {
        ws.current?.send(str);
      };
      const reconnect = () => {
        if (lockReconnect) {
          return;
        }
        lockReconnect = true;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        tt && clearTimeout(tt);
        //失败重连时间
        tt = setTimeout(function () {
          // console.log('重连中...');
          lockReconnect = false;
          closeWebSocket();
          ws.current = null;
          creatWebSocket();
        }, 4000);
      };
      useEffect(() => {
        webSocketInit();
        return () => {
          ws.current?.close();
        };
      }, []);
      return {
        wsData,
        readyState,
        closeWebSocket,
        reconnect,
        sendMessage,
      };
  }

使用

     const [isLocalPage, setIsLocalPage] = useState(true);
     const { wsData, readyState, closeWebSocket, reconnect } = useWebsocket({
                                        host: process.env.WS_HOST as string,
                                        port: process.env.WS_PORT as string,
                                        url: process.env.WS_DEVICE_URL as string,
                                      });
    useEffect(() => {
        if (Object.keys(wsData).length !== 0) {
          const newDate = cloneDeep(data);
        }
        // 如果是已关闭且是当前页面自动重连
        if (readyState.key === 3 && isLocalPage) {
          reconnect();
        }
        // 不是当前页面 清空 webSocket 此处为优化代码使用的,不需要可以直接删除。
        if (!isLocalPage) {
          closeWebSocket();
        }
      }, [wsData, readyState, isLocalPage]);
        // 判断是否当前页面
  useEffect(() => {
    document.addEventListener('visibilitychange', function () {
      // 页面变为不可见时触发
      if (document.visibilityState === 'hidden') {
        setIsLocalPage(false);
      }
      // 页面变为可见时触发
      if (document.visibilityState === 'visible') {
        setIsLocalPage(true);
      }
    });
  });

心跳检测

1.新建检测功能的class

    /* eslint-disable @typescript-eslint/no-unused-expressions */
    import type { Timeout } from 'ahooks/lib/useRequest/src/types';
    class heartCheck {
      timeout = 20000;
      timeoutObj: Timeout | null = null;
      serverTimeoutObj: Timeout | null = null;
      websoket: WebSocket | undefined = undefined;
      constructor(websoket: WebSocket) {
        this.websoket = websoket;
      }
      reset() {
        this.timeoutObj && clearTimeout(this.timeoutObj as Timeout);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj as Timeout);
      }
      start() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const self = this;
        this.reset();
        this.timeoutObj = setTimeout(() => {
           //20秒后发送心跳检测
          (this.websoket as WebSocket).send('HeartBeat');
          // console.log('ping');
          self.serverTimeoutObj = setTimeout(() => {
            //再过20秒后onmessage没有收到消息则直接关掉,触发重连
            (this.websoket as WebSocket).close();
          }, self.timeout);
        }, this.timeout);
      }
    }
export default heartCheck;

心跳检测使用

    1:在websocket实列化后创建心跳
      ws.current = new WebSocket(url);
      const heartbeat = new heartCheck(ws.current);
    2:onopen和onmessage使用
       heartbeat.start();
       注意!和后端协商好回应的心跳字符串,避免影响正常数据
    3:onclose使用 
       heartbeat.reset();