React Hook 实现 WebSocket

3,149 阅读3分钟

通过 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)
      }
    })
  }

最后,在这个代码中没有涉及到的场景就是 心跳机制,一般简单的需求可以不考虑,这块逻辑实现上也简单。这里就不加阐述了。有什么好的建议可以直接评论哦~。