正确使用 React hooks + WebSockets [英] Proper way of using React hooks + WebSockets

查看:29
本文介绍了正确使用 React hooks + WebSockets的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是我的第一次尝试.

import React, {useEffect} from 'react';导出默认函数 AppWs() {useEffect(() => {让 ws = new WebSocket('wss://ws.kraken.com/');ws.onopen = () =>console.log('ws 打开');ws.onclose = () =>console.log('ws 关闭');ws.onmessage = e =>{const message = JSON.parse(e.data);console.log('e', message);};返回 () =>{ws.close();}}, []);返回 (<div>钩子 + ws</div>)}

我向 useEffect 添加了连接和日志逻辑,提供了带有依赖项的空数组,一切都运行良好.直到我需要添加 pause 状态来暂停日志记录.

导出默认函数 AppWs() {const [isPaused, setPause] = useState(false);useEffect(() => {让 ws = new WebSocket('wss://ws.kraken.com/');ws.onopen = () =>console.log('ws 打开');ws.onclose = () =>console.log('ws 关闭');ws.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);console.log('e', message);};返回 () =>{ws.close();}}, []);返回 (<div><button onClick={() =>setPause(!isPaused)}>{isPaused ?'继续':'暂停'}

)}

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

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

导出默认函数 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 打开');setWs(wsClient);};wsClient.onclose = () =>console.log('ws 关闭');返回 () =>{wsClient.close();}}, []);useEffect(() => {如果(!ws)返回;ws.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);console.log('e', message);};}, [isPaused, ws]);返回 (<div><button onClick={() =>setPause(!isPaused)}>{isPaused ?'继续':'暂停'}

)}

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

解决方案

由于您只设置一次 web socket,我认为更好的方法是使用 ref 而不是状态:

useEffect 的顺序很重要.

正如 George 在评论中所建议的,在第一个 useEffect ws.current 被保存到一个变量中以确保当 close被称为它指的是同一个实例.

导出默认函数 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 打开");ws.current.onclose = () =>console.log(ws 关闭");const wsCurrent = ws.current;返回 () =>{wsCurrent.close();};}, []);useEffect(() => {如果(!ws.current)返回;ws.current.onmessage = e =>{如果(isPaused)返回;const message = JSON.parse(e.data);控制台日志(e",消息);};}, [isPaused]);返回 (<div><button onClick={() =>setPause(!isPaused)}>{是暂停?简历":暂停"}

);}

I need to connect to WebSockets server and log it's messages. 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.

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. Until I needed to add pause state to pause logging.

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.
Well, ok, done.
But I noticed re-connection to WS server after every time I click the button. This is clearly not what I want.

My next iteration was to use two useEffects: one for connection and one for message processing.

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. Please help to refactor the code on convince me that I'm using React hooks in a proper way. Thanks!

解决方案

As you are only setting the web socket once, I think a better approach is to use a ref instead of a state:

The order of useEffect is important.

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.

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

这篇关于正确使用 React hooks + WebSockets的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆