# useWebSocket自定义hook实现

借鉴ahooks

# 源代码

import { useEffect, useRef, useState, useMemo } from 'react';

// 获取最新值hook
const useLatest = value => {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}

// 函数持久化,避免重复声明函数,提升性能
const useMemoizedFn = fn => {
  const fnRef = useRef(fn);
  fnRef.current = useMemo(() => fn, [fn]);
  const memoizedFn = useRef();
  if (!memoizedFn.current) {
    memoizedFn.current = function (...args) {
      return fnRef.current.apply(this, args);
    }
  };
  return memoizedFn.current;
}

// 模拟componentWillUnmount
const useUnmount = fn => {
  const fnRef = useLatest(fn);
  useEffect(() => () => {
    fnRef.current();
  }, []);
}

// websocket连接状态
const READYSTATE = {
  Connecting: 0,
  Open: 1,
  Closing: 2,
  Closed: 3,
}

export default function useWebSocket(socketUrl, options = {}) {
  const {
    reconnectLimit = 3,
    reconnectInterval = 3000,
    manual = false,
    onOpen,
    onClose,
    onMessage,
    onError,
    protocols
  } = options;

  const onOpenRef = useLatest(onOpen);
  const onCloseRef = useLatest(onClose);
  const onMessageRef = useLatest(onMessage);
  const onErrorRef = useLatest(onError);

  const reconnectTimesRef = useRef(0);
  const reconnectTimerRef = useRef();
  const websocketRef = useRef();

  const unmountedRef = useRef(false);

  const [latestMessage, setLatestMessage] = useState();
  const [readyState, setReadyState] = useState(READYSTATE.Closed);

  const createWS = () => {
    if (reconnectTimerRef.current) { clearTimeout(reconnectTimerRef.current); }
    if (websocketRef, current) { websocketRef.current.close(); }

    const ws = new WebSocket(socketUrl, protocols);
    setReadyState(READYSTATE.Connecting);

    ws.onerror = (event) => {
      if (unmountedRef.current) {
        return;
      }
      reconnect();
      onErrorRef.current?.(event, ws);
      setReadyState(ws.readyState || READYSTATE.Closed);
    };
    ws.onopen = (event) => {
      if (unmountedRef.current) {
        return;
      }
      onOpenRef.current?.(event, ws);
      reconnectTimesRef.current = 0;
      setReadyState(ws.readyState || READYSTATE.Open);
    };
    ws.onmessage = (message) => {
      if (unmountedRef.current) {
        return;
      }
      onMessageRef.current?.(message, ws);
      setLatestMessage(message);
    };
    ws.onclose = (event) => {
      if (unmountedRef.current) {
        return;
      }
      reconnect();
      onCloseRef.current?.(event, ws);
      setReadyState(ws.readyState || READYSTATE.Closed);
    }

    websocketRef.current = ws;
  }

  const connect = () => {
    reconnectTimesRef.current = 0;
    createWS();
  };

  const reconnect = () => {
    if (reconnectTimesRef.current < reconnectLimit && 
    websocketRef.current?.readyState !== READYSTATE.Open
    ) {
      if (reconnectTimerRef.current) { clearTimeout(reconnectTimerRef.current); }
      reconnectTimerRef.current = setTimeout(() => {
        createWS();
        reconnectTimesRef.current++;
      }, reconnectInterval);
    }
  };

  const sendMessage = message => {
    if (readyState === READYSTATE.Open) {
      websocketRef.current?.send(message);
    } else {
      throw new Error('websocket disconnected');
    }
  };

  const disconnect = () => {
    if (reconnectTimerRef.current) {
      clearTimeout(reconnectTimerRef.current);
    }

    reconnectTimesRef.current = reconnectLimit;
    websocketRef.current?.close();
  };

  useEffect(() => {
    if (!manual) {
      connect();
    }
  }, [socketUrl, manual]);

  useUnmount(() => {
    unmountedRef.current = true;
    disconnect();
  });

  return {
    latestMessage,
    sendMessage: useMemoizedFn(sendMessage),
    connect: useMemoizedFn(connect),
    disconnect: useMemoizedFn(disconnect),
    readyState,
    webSocketIns: websocketRef.current,
  }
}
作者:王龙楷; 标签:原创; 提交时间: 7/4/2022, 9:20:18 PM