通过 hook 实现 WebSockt 更加简单
首先,在做一个需求的时候会看下是否有架子,在浏览大量的文章后,看到网上说可以用 socket.io-client . 然后自己经过自己简单封装。如下代码
import io from 'socket.io-client'
import { message } from 'antd'
// socket 地址和路径
const url = 'localhost:8080'
const path = '/exam-socket'
// 统一获取 socket client 方法
export const getClient = () => {
if (client) {
return client
} else {
message.error('socket 链接异常')
}
}
// socket handler 持久化
let client
// socket 初始化
export const clientInit = async () => {
client = io(url, {
reconnectionDelayMax: 10000,
query: {
auth: '123',
},
path,
transports: ['websocket'], // 必须配置
})
}
然后进行在页面需要的地方进行调用如下代码。
import React, { useState, useEffect } from 'react'
import { getClient } from './socket'
export default ()=>{
const [socketData ,setSocketData] = useState({});
useEffect(()=>{
// 其中 type 是跟后端约定好的。
getClient().on(type,(vl)=>{
setSocketData(vl)
})
//页面卸载 断开连接。
return getClient().disconnect()
},[])
}
大工告成,由于 socket.io-client 功能非常强大,什么重连、心跳、兼容都给搞进去了。但是这块也是较坑的 它需要后端使用 socket.io 进行相关接口的改造。否则你用不了。所有搞之前一定一定要跟后端一起搞。否则你真的用不了。到时候就会跟我一样 ,一言难尽。。。现场已经找不到图了,这块的问题是,这玩意后端要是没有用配套的 socket.io 你的前端是连上马上断了。会一直重连,持续新建非常多的 websockt 请求。。
然后后续的补救方案还是有的那就是自己用 websockt 原生结合 hook 搞一套 hook socket,
hook 版 socket
还是简单点直接上代码,后面在解释
import { useState, useRef, useEffect } from 'react'
const useWebsocket = ({ url, verify }) => {
const ws = useRef(null)
const [wsData, setMessage] = useState('')
const [readyState, setReadyState] = useState({ 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)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ws.current.onopen = (_e) => setReadyState(stateArr[ws.current?.readyState ?? 0])
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ws.current.onclose = (e) => {
setReadyState(stateArr[ws.current?.readyState ?? 0])
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ws.current.onerror = (e) => {
setReadyState(stateArr[ws.current?.readyState ?? 0])
}
ws.current.onmessage = (e) => {
setMessage(e.data)
}
} catch (error) {
console.log(error)
}
}
const webSocketInit = () => {
if (!ws.current || ws.current.readyState === 3) {
creatWebSocket()
}
}
// 关闭 WebSocket
const closeWebSocket = () => {
ws.current?.close()
}
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
}
}
export default useWebsocket
在上述代码中可以看见,我这边暴露出 四个参数。分别是 wsData (获得的 socket 数据)、readyState(当前 socket 状态)、closeWebSocket (关闭 socket)、reconnect(重连)。通过这几个简单的参数能够覆盖一般场景的需要。然后在代码中可以进行下面代码的操作。其中 verify 参数是控制是否有权限进行请求。可以根据 实际需求进行删除或新增。 看完原生的代码是不是感觉其实也挺简单的。重连啥的通过监听 readyState 状态进行相应操作。
import React, { useState, useEffect } from 'react'
import useWebsocket from '@/hooks/useWebsocket'
export default ()=>{
const [socketData ,setSocketData] = useState({});
const { wsData, readyState, closeWebSocket, reconnect } = useWebsocket({
url: `ws:/exam-socket`,
verify // 此参数控制是否有权限,请求该方法
})
useEffect(() => {
// 不在白名单人员之间不执行后续操作,不需要可以删除
if (!verify) {
return
}
const { data, type } = (wsData && JSON.parse(wsData)) || {}
switch (type) {// type 是跟后端约定的
case 'xxx1':
setSocketData({ ...socketData, review: data })
break
case 'xx2':
setSocketData({ ...socketData, pipelineResults: data })
break
default:
setSocketData({ ...socketData, ...data })
break
}
// 如果是已关闭且是当前页面自动重连
if (readyState.key === 3 && isLocalPage) {
reconnect()
}
// 不是当前页面 清空 webSocket 此处为优化代码使用的,不需要可以直接删除。
if (!isLocalPage) {
closeWebSocket()
}
}, [wsData, readyState, isLocalPage, verify])
对于 isLocalPage 感兴趣可以看下面代码是判断用户是否在当前页面。 此方法可以放在useEffect。
/*
** 判断用户是否离开当前页面,离开后不请求轮询接口,回到当前页面重新执行轮询
*/
const checkIsLocalPage = () => {
document.addEventListener('visibilitychange', function () {
// 页面变为不可见时触发
if (document.visibilityState === 'hidden') {
setIsLocalPage(false)
}
// 页面变为可见时触发
if (document.visibilityState === 'visible') {
setIsLocalPage(true)
}
})
}
最后,在这个代码中没有涉及到的场景就是 心跳机制,一般简单的需求可以不考虑,这块逻辑实现上也简单。这里就不加阐述了。有什么好的建议可以直接评论哦~。