【问题标题】:Proper way of using React hooks + WebSockets正确使用 React hooks + WebSockets
【发布时间】:2020-05-25 22:05:10
【问题描述】:

我需要连接到 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 钩子。谢谢!

【问题讨论】:

    标签: javascript reactjs websocket react-hooks


    【解决方案1】:

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

    useEffect 的顺序很重要。

    正如 George 在 cmets 中所建议的那样,第一个 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的建议!
    • 希望对您有所帮助
    • 当我使用此代码时,我注意到 websocket 关闭,然后在收到每条消息后重新打开。任何想法为什么这样做?
    • 顺便说一句,您应该将新创建的 websocket 存储到一个单独的变量中,并使用它进行清理。不是ws.current。其他一些代码可以改变 ws.current 并且旧的 websocket 实例不会被清理关闭。连接将保持打开状态并导致错误
    • @Alvaro - 如果你能请看线程stackoverflow.com/questions/69476080/…
    猜你喜欢
    • 1970-01-01
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 2020-08-20
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多