React hook实现websocket封装
新建websocket.ts文件
import {useState, useRef, useEffect} from 'react' const useWebsocket = ({ url:string, verify }) => { ? ? const ws = useRef<WebSocket | null>(null) ? ? // socket 数据 ? ? const [wsData, setMessage] = useState({}) ? ? // ?socket 状态 ? ? const [readyState, setReadyState] = useState<any>({ key: 0, value: '正在连接中' }) ? ? 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) ? ? ? ? } ? ? } ? ? 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) ? ? ? ? } ? ? } ? ? useEffect(() => { ? ? ? ? verify && webSocketInit() ? ? ? ? return () => { ? ? ? ? ? ? ws.current?.close() ? ? ? ? } ? ? }, [ws,verify]) ? ? return { ? ? ? ? wsData, ? ? ? ? readyState, ? ? ? ? closeWebSocket, ? ? ? ? reconnect, ? ? ? ? sendMessage, ? ? } } export default useWebsocket
这里一共暴露出四个参数。分别是
wsData (获得的 socket 数据) readyState (当前 socket 状态) closeWebSocket (关闭 socket) reconnect (重连)通过这几个简单的参数能够覆盖一般场景的需要。其中 verify 参数是控制是否有权限进行请求。可以根据 实际需求进行删除或新增。
重连啥的通过监听 readyState 状态进行相应操作。
下面代码为使用方法:
import React, { useState, useEffect } from 'react' import useWebsocket from 'tools/webSocket' export default function () { ?? ?const [isLocalPage, setIsLocalPage] = useState(true) ?? ?const { wsData, readyState, closeWebSocket, reconnect } = useWebsocket({ ? ? ?? ?url: 'ws://ip:端口', // 此参数为websocket地址 ? ? ?? ?verify // 此参数控制是否有权限,请求该方法 ??? ? }) ?? ?useEffect(() => { ? ? ?? ?// 不在白名单人员之间不执行后续操作,不需要可以删除 ? ? ?? ?if (!verify) { ? ? ? ?? ??? ?return ?? ? ? ?} ?? ? ? ? ?? ? ? ?// 接受到socket数据, 进行业务逻辑处理 ?? ? ? ?if (Object.keys(wsData).length !== 0) { ?? ? ? ??? ?console.log(wsData) ?? ? ? ?} ?? ? ? ? ?? ? ? ?// 如果是已关闭且是当前页面自动重连 ?? ? ? ?if (readyState.key === 3 && isLocalPage) { ?? ? ? ? ?reconnect() ?? ? ? ?} ?? ? ? ?// 不是当前页面 清空 webSocket 此处为优化代码使用的,不需要可以直接删除。 ?? ? ? ?if (!isLocalPage) { ?? ? ? ? ?closeWebSocket() ?? ? ? ?} ? ?? ?}, [wsData, readyState, isLocalPage, verify]) ? }
对于 isLocalPage 感兴趣可以看下面代码是判断用户是否在当前页面。 此方法可以放在useEffect。
/* ?** 判断用户是否离开当前页面,离开后不请求轮询接口,回到当前页面重新执行轮询 ?*/ useEffect(() => { ? ? ? document.addEventListener('visibilitychange', function () { ? ? ? ? ? // 页面变为不可见时触发 ? ? ? ? ? if (document.visibilityState === 'hidden') { ? ? ? ? ? ? ? setIsLocalPage(false) ? ? ? ? ? } ? ? ? ? ? // 页面变为可见时触发 ? ? ? ? ? if (document.visibilityState === 'visible') { ? ? ? ? ? ? ? setIsLocalPage(true) ? ? ? ? ? } ? ? ? }) ? })
最后,在这个代码中没有涉及到的场景就是 心跳机制,一般简单的需求可以不考虑,这块逻辑实现上也比较简单,这里就不多加阐述了。
react自定义hook解决websocket连接,useWebSocket
react自定义hook,useWebSocket
1、描述
本来项目的告警和消息提醒是用的接口30秒调用一次,这次要改成webSocket传输。
因为前端是用的https,后端用的http,后端的socket只支持ws不支持wss,这里使用了webpack-dev-server的proxy代理了一下。
target:ws目标地址、pathRewrite:地址重写,这里是把/aapp_socket重写成aapp/websocket,ws:是否开启socket,secure: 默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false ,changeOrigin:是否跨域。差不多就这个意思
? '/aapp_socket': { ? ? ? ? ? ? ? ? target: `ws://xxx.xxx.xxx/`, ? ? ? ? ? ? ? ? pathRewrite: { ? ? ? ? ? ? ? ? ? ? '^/aapp_socket': 'aapp/websocket', ? ? ? ? ? ? ? ? }, ? ? ? ? ? ? ? ? ws: true, ? ? ? ? ? ? ? ? secure: false, ? ? ? ? ? ? ? ? changeOrigin: true, ? ? ? ? ? ? },
使用连接的地址:
`wss://localhost:3000/aapp_socket`;
实际的访问的地址就是:
`ws://xxx.xxx.xxx/aapp/websocket
2、代码
这里socket,没有配置心跳监测,还是通过我主动去推送来获取信息。这里是获取告警数和消息数量,
首先绑定websocket的事件。主要就是在message的事件中,连接成功后端返回的是sucess,就不做操作。后面就是判断返回的消息格式是否正确,如果不正确就重新连接。
还可以把获取消息的时间间隔,和重新连接间隔,地址等变量抽出来,作为参数传进来。
import {useCallback, useRef, useState, useEffect} from 'react'; const token = window.localStorage.getItem('authorization'); const userId = JSON.parse(window.localStorage.getItem('userInfo') || '')?.id; // 获取告警数量 const UNREAD_WARN_COUNT = 'UNREAD_WARN_COUNT'; // 获取消息数量 const UNREAD_MSG_COUNT = 'UNREAD_MSG_COUNT'; // 获取消息的间隔 const INT_TIME = 5000; // websocket状态 const webSocketStatus = { ? ? CONNECTING: 0, ? ? OPEN: 1, ? ? CLOSING: 2, ? ? CLOSED: 3, }; const useWebSocket = () => { ? ? const [reset, setReset] = useState<boolean>(false); ? ? const socket = useRef<WebSocket>(); ? ? const sendCount = useRef<number>(1); ? ? const [alarmCount, setAlarmCount] = useState<number>(0); ? ? const [messageCount, setMessageCount] = useState<number>(0); ? ? // 开启事件,主动获取数据 ? ? const socketOnOpen = useCallback(() => { ? ? ? ? // 判断连接状态是不是open ? ? ? ? if (socket?.current?.readyState === webSocketStatus.OPEN) { ? ? ? ? ? ? // 第一次加载触发一次 ? ? ? ? ? ? socket?.current?.send(JSON.stringify({businessKey: [UNREAD_MSG_COUNT, UNREAD_WARN_COUNT]})); ? ? ? ? } ? ? ? ? const timer = setInterval(() => { ? ? ? ? ? ? if (socket?.current?.readyState === webSocketStatus.OPEN) { ? ? ? ? ? ? ? ? socket?.current?.send(JSON.stringify({businessKey: [UNREAD_MSG_COUNT, UNREAD_WARN_COUNT]})); ? ? ? ? ? ? } ? ? ? ? }, INT_TIME); ? ? ? ? // 返回信息出错清除定时器 ? ? ? ? if (sendCount.current === 0) { ? ? ? ? ? ? clearInterval(timer); ? ? ? ? ? ? setReset(true); ? ? ? ? } ? ? }, [sendCount]); ? ? // 关闭事件重新连接 ? ? const socketOnClose = useCallback(() => { ? ? ? ? setReset(true); ? ? }, []); ? ? // 出错事件 ? ? const socketOnError = useCallback((err: any) => { ? ? ? ? console.log('err: ', err); ? ? }, []); ? ? // 收发信息 ? ? const socketOnMessage = useCallback( ? ? ? ? (e: any) => { ? ? ? ? ? ? if (e.data === 'success') return; ? ? ? ? ? ? const alarmCountObj = JSON.parse(e.data); ? ? ? ? ? ? const paramNameArr = Object.keys(alarmCountObj); ? ? ? ? ? ? // 判断返回告警保持连接否则断开连接 ? ? ? ? ? ? if (paramNameArr[1] === 'UNREAD_WARN_COUNT') { ? ? ? ? ? ? ? ? sendCount.current += 1; ? ? ? ? ? ? ? ? setAlarmCount(alarmCountObj.UNREAD_WARN_COUNT); ? ? ? ? ? ? ? ? setMessageCount(alarmCountObj.UNREAD_MSG_COUNT); ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? sendCount.current = 0; ? ? ? ? ? ? } ? ? ? ? }, ? ? ? ? [sendCount], ? ? ); ? ? // 初始化连接socket ? ? const socketInit = useCallback(() => { ? ? ? ? try { ? ? ? ? ? ? const scoketUrl = `wss://${window.location.host}/aapp_socket/${userId}/${token}`; ? ? ? ? ? ? const socketObj = new WebSocket(scoketUrl); ? ? ? ? ? ? socketObj.addEventListener('close', socketOnClose); ? ? ? ? ? ? socketObj.addEventListener('error', socketOnError); ? ? ? ? ? ? socketObj.addEventListener('message', socketOnMessage); ? ? ? ? ? ? socketObj.addEventListener('open', socketOnOpen); ? ? ? ? ? ? socket.current = socketObj; ? ? ? ? ? ? sendCount.current = 1; ? ? ? ? } catch (err) { ? ? ? ? ? ? console.log('err: ', err); ? ? ? ? } ? ? }, [socketOnClose, socketOnError, socketOnMessage, socketOnOpen]); ? ? // 初始化连接socket ? ? useEffect(() => { ? ? ? ? socketInit(); ? ? }, [socketInit]); ? ? // 断线重连 ? ? useEffect(() => { ? ? ? ? if (!reset) return; ? ? ? ? setTimeout(() => { ? ? ? ? ? ? socketInit(); ? ? ? ? ? ? setReset(false); ? ? ? ? }, 30000); ? ? }, [reset, socketInit]); ? ? return [alarmCount, messageCount]; }; export default useWebSocket;
使用
?// 告警socket连接 ? ? const [alarmCount, messageCount] = useWebSocket();
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
查看更多关于React hook实现简单的websocket封装方式的详细内容...