React?hook實(shí)現(xiàn)簡單的websocket封裝方式
React hook實(shí)現(xiàn)websocket封裝
新建websocket.ts文件
import {useState, useRef, useEffect} from 'react' const useWebsocket = ({ url:string, verify }) => { ? ? const ws = useRef<WebSocket | null>(null) ? ? // socket 數(shù)據(jù) ? ? const [wsData, setMessage] = useState({}) ? ? // ?socket 狀態(tài) ? ? const [readyState, setReadyState] = useState<any>({ key: 0, value: '正在連接中' }) ? ? const creatWebSocket = () => { ? ? ? ? const stateArr = [ ? ? ? ? ? ? {key: 0, value: '正在連接中'}, ? ? ? ? ? ? {key: 1, value: '已經(jīng)連接并且可以通訊'}, ? ? ? ? ? ? {key: 2, value: '連接正在關(guān)閉'}, ? ? ? ? ? ? {key: 3, value: '連接已關(guān)閉或者沒有連接成功'}, ? ? ? ? ] ? ? ? ? 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() ? ? ? ? } ? ? } ? ? // ?關(guān)閉 WebSocket ? ? const closeWebSocket = () => { ? ? ? ? ws.current?.close() ? ? } ? ? // 發(fā)送數(shù)據(jù) ? ? 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
這里一共暴露出四個(gè)參數(shù)。分別是
wsData
(獲得的 socket 數(shù)據(jù))readyState
(當(dāng)前 socket 狀態(tài))closeWebSocket
(關(guān)閉 socket)reconnect
(重連)
通過這幾個(gè)簡單的參數(shù)能夠覆蓋一般場景的需要。其中 verify 參數(shù)是控制是否有權(quán)限進(jìn)行請求??梢愿鶕?jù) 實(shí)際需求進(jìn)行刪除或新增。
重連啥的通過監(jiān)聽 readyState 狀態(tài)進(jìn)行相應(yīng)操作。
下面代碼為使用方法:
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:端口', // 此參數(shù)為websocket地址 ? ? ?? ?verify // 此參數(shù)控制是否有權(quán)限,請求該方法 ??? ? }) ?? ?useEffect(() => { ? ? ?? ?// 不在白名單人員之間不執(zhí)行后續(xù)操作,不需要可以刪除 ? ? ?? ?if (!verify) { ? ? ? ?? ??? ?return ?? ? ? ?} ?? ? ? ? ?? ? ? ?// 接受到socket數(shù)據(jù), 進(jìn)行業(yè)務(wù)邏輯處理 ?? ? ? ?if (Object.keys(wsData).length !== 0) { ?? ? ? ??? ?console.log(wsData) ?? ? ? ?} ?? ? ? ? ?? ? ? ?// 如果是已關(guān)閉且是當(dāng)前頁面自動重連 ?? ? ? ?if (readyState.key === 3 && isLocalPage) { ?? ? ? ? ?reconnect() ?? ? ? ?} ?? ? ? ?// 不是當(dāng)前頁面 清空 webSocket 此處為優(yōu)化代碼使用的,不需要可以直接刪除。 ?? ? ? ?if (!isLocalPage) { ?? ? ? ? ?closeWebSocket() ?? ? ? ?} ? ?? ?}, [wsData, readyState, isLocalPage, verify]) ? }
對于 isLocalPage 感興趣可以看下面代碼是判斷用戶是否在當(dāng)前頁面。 此方法可以放在useEffect。
/* ?** 判斷用戶是否離開當(dāng)前頁面,離開后不請求輪詢接口,回到當(dāng)前頁面重新執(zhí)行輪詢 ?*/ useEffect(() => { ? ? ? document.addEventListener('visibilitychange', function () { ? ? ? ? ? // 頁面變?yōu)椴豢梢姇r(shí)觸發(fā) ? ? ? ? ? if (document.visibilityState === 'hidden') { ? ? ? ? ? ? ? setIsLocalPage(false) ? ? ? ? ? } ? ? ? ? ? // 頁面變?yōu)榭梢姇r(shí)觸發(fā) ? ? ? ? ? if (document.visibilityState === 'visible') { ? ? ? ? ? ? ? setIsLocalPage(true) ? ? ? ? ? } ? ? ? }) ? })
最后,在這個(gè)代碼中沒有涉及到的場景就是 心跳機(jī)制,一般簡單的需求可以不考慮,這塊邏輯實(shí)現(xiàn)上也比較簡單,這里就不多加闡述了。
react自定義hook解決websocket連接,useWebSocket
react自定義hook,useWebSocket
1、描述
本來項(xiàng)目的告警和消息提醒是用的接口30秒調(diào)用一次,這次要改成webSocket傳輸。
因?yàn)榍岸耸怯玫膆ttps,后端用的http,后端的socket只支持ws不支持wss,這里使用了webpack-dev-server的proxy代理了一下。
target:ws目標(biāo)地址、pathRewrite:地址重寫,這里是把/aapp_socket重寫成aapp/websocket,ws:是否開啟socket,secure: 默認(rèn)情況下不接收轉(zhuǎn)發(fā)到https的服務(wù)器上,如果希望支持,可以設(shè)置為false ,changeOrigin:是否跨域。差不多就這個(gè)意思
? '/aapp_socket': { ? ? ? ? ? ? ? ? target: `ws://xxx.xxx.xxx/`, ? ? ? ? ? ? ? ? pathRewrite: { ? ? ? ? ? ? ? ? ? ? '^/aapp_socket': 'aapp/websocket', ? ? ? ? ? ? ? ? }, ? ? ? ? ? ? ? ? ws: true, ? ? ? ? ? ? ? ? secure: false, ? ? ? ? ? ? ? ? changeOrigin: true, ? ? ? ? ? ? },
使用連接的地址:
`wss://localhost:3000/aapp_socket`;
實(shí)際的訪問的地址就是:
`ws://xxx.xxx.xxx/aapp/websocket
2、代碼
這里socket,沒有配置心跳監(jiān)測,還是通過我主動去推送來獲取信息。這里是獲取告警數(shù)和消息數(shù)量,
首先綁定websocket的事件。主要就是在message的事件中,連接成功后端返回的是sucess,就不做操作。后面就是判斷返回的消息格式是否正確,如果不正確就重新連接。
還可以把獲取消息的時(shí)間間隔,和重新連接間隔,地址等變量抽出來,作為參數(shù)傳進(jìn)來。
import {useCallback, useRef, useState, useEffect} from 'react'; const token = window.localStorage.getItem('authorization'); const userId = JSON.parse(window.localStorage.getItem('userInfo') || '')?.id; // 獲取告警數(shù)量 const UNREAD_WARN_COUNT = 'UNREAD_WARN_COUNT'; // 獲取消息數(shù)量 const UNREAD_MSG_COUNT = 'UNREAD_MSG_COUNT'; // 獲取消息的間隔 const INT_TIME = 5000; // websocket狀態(tài) 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); ? ? // 開啟事件,主動獲取數(shù)據(jù) ? ? const socketOnOpen = useCallback(() => { ? ? ? ? // 判斷連接狀態(tài)是不是open ? ? ? ? if (socket?.current?.readyState === webSocketStatus.OPEN) { ? ? ? ? ? ? // 第一次加載觸發(fā)一次 ? ? ? ? ? ? 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); ? ? ? ? // 返回信息出錯(cuò)清除定時(shí)器 ? ? ? ? if (sendCount.current === 0) { ? ? ? ? ? ? clearInterval(timer); ? ? ? ? ? ? setReset(true); ? ? ? ? } ? ? }, [sendCount]); ? ? // 關(guān)閉事件重新連接 ? ? const socketOnClose = useCallback(() => { ? ? ? ? setReset(true); ? ? }, []); ? ? // 出錯(cuò)事件 ? ? const socketOnError = useCallback((err: any) => { ? ? ? ? console.log('err: ', err); ? ? }, []); ? ? // 收發(fā)信息 ? ? 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();
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
react ant-design Select組件下拉框map不顯示的解決
這篇文章主要介紹了react ant-design Select組件下拉框map不顯示的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03React?正確使用useCallback?useMemo的方式
這篇文章主要介紹了React?正確使用useCallback?useMemo的方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08使用react-native-image-viewer實(shí)現(xiàn)大圖預(yù)覽
這篇文章主要介紹了使用react-native-image-viewer實(shí)現(xiàn)大圖預(yù)覽,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09在React項(xiàng)目中使用iframe嵌入一個(gè)網(wǎng)站的步驟
本文介紹了如何在React項(xiàng)目中通過iframe嵌入百度網(wǎng)站的步驟,首先創(chuàng)建一個(gè)Baidu.js組件,并在該組件中設(shè)置iframe來加載百度,然后在App.js中引入并使用Baidu組件,還討論了因安全策略可能無法加載某些網(wǎng)站的問題,需要的朋友可以參考下2024-09-09