正确使用 React hooks + WebSockets

IT技术 javascript reactjs websocket react-hooks
2021-03-26 11:55:28

我需要连接到 WebSockets 服务器并记录它的消息。使用 React 类组件,我会将这个逻辑放在componentDidMount生命周期钩子中并愉快地继续,但我不确定如何使用钩子正确实现它。

这是我的第一次尝试。

import React, {useEffect} from 'react';

export default function AppWs() {
  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>hooks + ws</div>
  )
}

我向 中添加了连接和日志逻辑useEffect,提供了具有依赖项的空数组,并且一切正常。直到我需要添加pause状态以暂停日志记录。

export default function AppWs() {
  const [isPaused, setPause] = useState(false);

  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

ESLint 开始对我大喊大叫,我应该将isPaused状态作为依赖添加useEffect.
嗯,好的,完成了。
但是我注意到每次单击按钮后都会重新连接到 WS 服务器。这显然不是我想要的。

我的下一次迭代是使用两个useEffects:一个用于连接,一个用于消息处理。

export default function AppWs() {
  const [isPaused, setPause] = useState(false);
  const [ws, setWs] = useState(null);

  useEffect(() => {
    const wsClient = new WebSocket('wss://ws.kraken.com/');
    wsClient.onopen = () => {
      console.log('ws opened');
      setWs(wsClient);
    };
    wsClient.onclose = () => console.log('ws closed');

    return () => {
      wsClient.close();
    }
  }, []);

  useEffect(() => {
    if (!ws) return;

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };
  }, [isPaused, ws]);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

这按预期工作,但我有一种感觉,我错过了一些东西,这个任务可以更容易地解决,一个useEffect. 请帮助重构代码,让我相信我正在以正确的方式使用 React 钩子。谢谢!

1个回答

由于您只设置一次网络套接字,我认为更好的方法是使用 ref 而不是状态:

的顺序useEffect很重要。

正如 George 在评论中所建议的那样,第一个useEffect ws.current被保存到一个变量中,以确保在close被调用时它指的是同一个实例。

export default function AppWs() {
    const [isPaused, setPause] = useState(false);
    const ws = useRef(null);

    useEffect(() => {
        ws.current = new WebSocket("wss://ws.kraken.com/");
        ws.current.onopen = () => console.log("ws opened");
        ws.current.onclose = () => console.log("ws closed");

        const wsCurrent = ws.current;

        return () => {
            wsCurrent.close();
        };
    }, []);

    useEffect(() => {
        if (!ws.current) return;

        ws.current.onmessage = e => {
            if (isPaused) return;
            const message = JSON.parse(e.data);
            console.log("e", message);
        };
    }, [isPaused]);

    return (
        <div>
            <button onClick={() => setPause(!isPaused)}>
                {isPaused ? "Resume" : "Pause"}
            </button>
        </div>
    );
}
感谢您的回答和建议useRef
2021-05-22 11:55:28
我希望它会有所帮助
2021-05-27 11:55:28
@Alvaro - 希望你能看看线程stackoverflow.com/questions/69476080/...
2021-06-11 11:55:28
当我使用此代码时,我注意到 websocket 关闭,然后在收到每条消息后重新打开。任何想法为什么这样做?
2021-06-14 11:55:28
顺便说一句,您应该将新创建的 websocket 存储到一个单独的变量中,并将其用于清理。不是ws.current. 一些其他代码可以改变ws.current旧的 websocket 实例不会被清理关闭。连接将保持打开状态并导致错误
2021-06-17 11:55:28