简体   繁体   English

正确使用 React hooks + WebSockets

[英]Proper way of using React hooks + WebSockets

I need to connect to WebSockets server and log it's messages.我需要连接到 WebSockets 服务器并记录它的消息。 With React class component I'd put this logic in componentDidMount lifecycle hook and move on happily, but I'm not sure how to properly implement it with hooks.使用 React 类组件,我会将这个逻辑放在componentDidMount生命周期钩子中并愉快地继续前进,但我不确定如何使用钩子正确实现它。

Here's my first attempt.这是我的第一次尝试。

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

I added connection and log logic to useEffect , provided empty array with dependencies, and everything worked great.我向useEffect添加了连接和日志逻辑,提供了带有依赖项的空数组,一切都很好。 Until I needed to add pause state to pause logging.直到我需要添加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 started to yell at me that I should add isPaused state as a dependency to useEffect . ESLint 开始对我大喊大叫,我应该将isPaused状态添加为useEffect的依赖项。
Well, ok, done.嗯,好的,完成了。
But I noticed re-connection to WS server after every time I click the button.但是我注意到每次单击按钮后都会重新连接到 WS 服务器。 This is clearly not what I want.这显然不是我想要的。

My next iteration was to use two useEffect s: one for connection and one for message processing.我的下一次迭代是使用两个useEffect :一个用于连接,一个用于消息处理。

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

This works as expected, but I have a feeling that I miss something and this task can be solved easier, with one useEffect .这按预期工作,但我有一种感觉,我错过了一些东西,这个任务可以通过一个useEffect更容易地解决。 Please help to refactor the code on convince me that I'm using React hooks in a proper way.请帮助重构代码,让我相信我正在以正确的方式使用 React 钩子。 Thanks!谢谢!

As you are only setting the web socket once, I think a better approach is to use a ref instead of a state:由于您只设置一次网络套接字,我认为更好的方法是使用 ref 而不是状态:

The order of useEffect is important. useEffect的顺序很重要。

As suggested by George in the comments, in the first useEffect ws.current is saved to a variable to make sure that when close is called it refers to the same instance.正如 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>
    );
}
 useEffect(() => {
const socket = new WebSocket('wss://ws.kraken.com');
socket.addEventListener('message', function (event) {
  const a = JSON.parse(event.data);
  setPriceWebSocket(a);

  const amin = socket.send(
    JSON.stringify({
      event: 'subscribe',
      pair: ['XBT/USD', 'XBT/EUR', 'ADA/USD'],
      subscription: { name: 'ticker' },
    }),
  );
  

});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM