使用React Hooks重新连接Web套接字

苏肖万

我想与服务器建立Web套接字连接。如果连接关闭5秒钟后重新连接。我正在使用React Hooks,到目前为止已经实现了

import React, { useRef, useState, useEffect } from 'react';`
import './App.css';
function App() {
 const wsClient = useRef(null);
 const [wsState, setWsState] = useState(true)
 useEffect(() => {
    wsClient.current = new WebSocket(url);
    console.log("Trying to open ws");
    setWsState(true)
    
    wsClient.current.onopen = () => {
      console.log('ws opened');
      wsClient.current.send('{"type" : "hello"}')
    };
    wsClient.current.onclose = (event) => {
      // Parse event code and log
      setTimeout(() => {setWsState(false)}, 5000)
      console.log('ws closed');
    }
    wsClient.current.onmessage = ((event) => {
      // DO YOUR JOB
    })
    return () => {
      console.log('ws closed');
      wsClient.current.close();
    }
  }, [wsState]);
  return (
      <div className="App">
        <Header />
        <MainBody />
      </div>
  );
}

当它无法与服务器连接时,这将导致重试次数成倍增加,如果我删除setTimeout并使用简单的方法,setState它可以正常工作。我无法理解问题,也无法提出实现目标的最佳实践。

约书亚记

我不认为效果是最好的选择。如果是应用程序级的,则在其自己的模块中实现它可能更简单,并在需要时将其引入。

尽管如此,要使其正常工作,您应该考虑管理两个独立的生命周期:组件生命周期和websocket生命周期。要使其按需工作,必须确保一个状态中的每个状态更改与另一个状态中的状态更改保持一致。

首先,请记住,每次更改数组中的依赖关系时,效果都会运行。因此,在您的示例中,每次设置时,效果都会运行wsState

要记住的另一件事是,每次wsState更改时都会调用清理函数,效果是执行两次(将其设置为true“打开”和false“关闭”)。这意味着,当您创建一个新的套接字而无法连接时,将触发close事件,并将状态更改排入队列。

每次尝试连接时,它都会设置wsStatetrue(将重新播放效果排队),尝试连接并失败,最后设置另一个超时,将状态更新为false但是,在效果再次运行之前,请尝试将状态设置为true等。

要解决此问题,请从效果生命周期开始。您的效果什么时候开始?什么时候应该清理?一些想法:

  1. 该效果应在第一个渲染期间运行一次,但不应在后续渲染期间运行
  2. WebSocket断开连接时应清除该影响
  3. 效果应在超时后重新运行,从而触发重新连接

这对组件意味着什么?您不想将WS状态包括为依赖项。但是,您确实需要状态来触发它以在超时后重新运行。

看起来是这样的:

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

const URL = 'ws://localhost:8888';

export default function App() {

  const clientRef = useRef(null);
  const [waitingToReconnect, setWaitingToReconnect] = useState(null);
  const [messages, setMessages] = useState([]);
  const [isOpen, setIsOpen] = useState(false);

  function addMessage(message) {
    setMessages([...messages, message]); 
  }

  useEffect(() => {

    if (waitingToReconnect) {
      return;
    }

    // Only set up the websocket once
    if (!clientRef.current) {
      const client = new WebSocket(URL);
      clientRef.current = client;

      window.client = client;

      client.onerror = (e) => console.error(e);

      client.onopen = () => {
        setIsOpen(true);
        console.log('ws opened');
        client.send('ping');
      };

      client.onclose = () => {

        if (clientRef.current) {
          // Connection failed
          console.log('ws closed by server');
        } else {
          // Cleanup initiated from app side, can return here, to not attempt a reconnect
          console.log('ws closed by app component unmount');
          return;
        }

        if (waitingToReconnect) {
          return;
        };

        // Parse event code and log
        setIsOpen(false);
        console.log('ws closed');

        // Setting this will trigger a re-run of the effect,
        // cleaning up the current websocket, but not setting
        // up a new one right away
        setWaitingToReconnect(true);

        // This will trigger another re-run, and because it is false,
        // the socket will be set up again
        setTimeout(() => setWaitingToReconnect(null), 5000);
      };

      client.onmessage = message => {
        console.log('received message', message);
        addMessage(`received '${message.data}'`);
      };


      return () => {

        console.log('Cleanup');
        // Dereference, so it will set up next time
        clientRef.current = null;

        client.close();
      }
    }

  }, [waitingToReconnect]);

  return (
    <div>
      <h1>Websocket {isOpen ? 'Connected' : 'Disconnected'}</h1>
      {waitingToReconnect && <p>Reconnecting momentarily...</p>}
      {messages.map(m => <p>{JSON.stringify(m, null, 2)}</p>)}
    </div>
  );
}

在此示例中,将跟踪连接状态,但不会跟踪useEffect依赖项。waitingForReconnect是的。并在关闭连接时设置,并在一段时间后取消设置以触发重新连接尝试。

清理也会触发关闭,因此我们需要onClose通过查看客户端是否已被取消引用来区分

如您所见,这种方法相当复杂,并且将WS生命周期与组件生命周期联系在一起(从技术角度来说,如果您是在应用程序级别执行此操作,则可以)。

但是,一个主要警告是,过时的关闭确实很容易遇到问题例如,addMessage可以访问局部变量消息,但是由于addMessage没有作为依赖项传递,因此每次运行效果时都不能两次调用它,否则它将覆盖最后一条消息。(本质上,这不是覆盖,而是用旧的,“陈旧”的值messages与新值相连接来覆盖状态。调用它十次,您只会看到最后一个值。)

因此,您可以添加addMessage依赖项,但随后您将断开并重新连接每个渲染的websocket。您可以摆脱addMessages,而只需将该逻辑移入效果中,但是每次更新messages数组时,它都会重新运行(频率比每个渲染器都低,但仍然非常频繁)。

因此,我建议您在应用程序生命周期之外设置您的客户端。您可以使用自定义钩子来处理传入消息,也可以直接在效果中直接处理它们。

这是一个例子:

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

const URL = 'ws://localhost:8888';


function reconnectingSocket(url) {
  let client;
  let isConnected = false;
  let reconnectOnClose = true;
  let messageListeners = [];
  let stateChangeListeners = [];

  function on(fn) {
    messageListeners.push(fn);
  }

  function off(fn) {
    messageListeners = messageListeners.filter(l => l !== fn);
  }

  function onStateChange(fn) {
    stateChangeListeners.push(fn);
    return () => {
      stateChangeListeners = stateChangeListeners.filter(l => l !== fn);
    };
  }

  function start() {
    client = new WebSocket(URL);

    client.onopen = () => {
      isConnected = true;
      stateChangeListeners.forEach(fn => fn(true));
    }

    const close = client.close;

    // Close without reconnecting;
    client.close = () => {
      reconnectOnClose = false;
      close.call(client);
    }

    client.onmessage = (event) => {
      messageListeners.forEach(fn => fn(event.data));
    }

    client.onerror = (e) => console.error(e);

    client.onclose = () => {

      isConnected = false;
      stateChangeListeners.forEach(fn => fn(false));

      if (!reconnectOnClose) {
        console.log('ws closed by app');
        return;
      }

      console.log('ws closed by server');

      setTimeout(start, 3000);
    }
  }

  start();

  return {
    on,
    off,
    onStateChange,
    close: () => client.close(),
    getClient: () => client,
    isConnected: () => isConnected,
  };
}


const client = reconnectingSocket(URL);


function useMessages() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    function handleMessage(message) {
      setMessages([...messages, message]);
    }
    client.on(handleMessage);
    return () => client.off(handleMessage);
  }, [messages, setMessages]);

  return messages;
}



export default function App() {

  const [message, setMessage] = useState('');
  const messages = useMessages();
  const [isConnected, setIsConnected] = useState(client.isConnected());

  useEffect(() => {
    return client.onStateChange(setIsConnected);
  }, [setIsConnected]);

  useEffect(() => {
    if (isConnected) {
      client.getClient().send('hi');
    }
  }, [isConnected]);

  function sendMessage(e) {
    e.preventDefault();
    client.getClient().send(message);
    setMessage('');
  }

  return (
    <div>
      <h1>Websocket {isConnected ? 'Connected' : 'Disconnected'}</h1>

      <form onSubmit={sendMessage}>
        <input value={message} onChange={e => setMessage(e.target.value)} />
        <button type="submit">Send</button>
      </form>

      {messages.map(m => <p>{JSON.stringify(m, null, 2)}</p>)}
    </div>
  );
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Web套接字在OpenShift上断开连接(使用WildFly 8.2.1)

来自分类Dev

PHP Web套接字未使用SSL连接

来自分类Dev

如何使用AT命令可靠地拆除和重新连接TCP / IP套接字

来自分类Dev

Web套接字问题使用Jetty Web服务器进行安全连接

来自分类Dev

使用标准Web套接字客户端连接到Socket.io服务器

来自分类Dev

使用rxjs创建一个可观察对象,稍后将其连接到Web套接字

来自分类Dev

使用Angular JS,在更改路线时关闭Web套接字连接的正确方法是什么?

来自分类Dev

如何每个Web套接字仅使用一个DB连接?

来自分类Dev

使用Nginx将Web套接字连接重定向到新创建的Docker容器

来自分类Dev

使用rxjs创建一个可观察对象,稍后将其连接到Web套接字

来自分类Dev

使用 python3 套接字连接到 Web 服务器

来自分类Dev

使用Unix套接字的JDBC MySQL连接

来自分类Dev

无法使用javascript连接网络套接字

来自分类Dev

使用asynctask android连接到套接字

来自分类Dev

无法使用javascript连接网络套接字

来自分类Dev

Python套接字重新连接

来自分类Dev

使用Jasmine测试Web套接字

来自分类Dev

如何开始使用Web套接字?

来自分类Dev

使用套接字从Web Java获取图片

来自分类Dev

使用Jasmine测试Web套接字

来自分类Dev

如何开始使用Web套接字?

来自分类Dev

使用sails.io.js在Ionic应用程序与Sails API之间建立Web套接字连接

来自分类Dev

如何使用OS X的“ security add-trusted-cert”命令指定证书的策略约束?(用于SSL Web套接字连接)

来自分类Dev

如何使用其 IP 地址从 Web 浏览器连接到 python 套接字服务器?

来自分类Dev

使用全双工套接字通信进行SSL重新协商

来自分类Dev

ZeroMQ:重新绑定套接字时使用地址错误

来自分类Dev

ZeroMQ:重新绑定套接字时,地址使用中错误

来自分类Dev

在哪里使用React中的Hooks定义需要从全局状态获取数据的套接字事件监听器

来自分类Dev

python套接字如何使用相同的套接字连接正确重定向http / s请求?

Related 相关文章

  1. 1

    Web套接字在OpenShift上断开连接(使用WildFly 8.2.1)

  2. 2

    PHP Web套接字未使用SSL连接

  3. 3

    如何使用AT命令可靠地拆除和重新连接TCP / IP套接字

  4. 4

    Web套接字问题使用Jetty Web服务器进行安全连接

  5. 5

    使用标准Web套接字客户端连接到Socket.io服务器

  6. 6

    使用rxjs创建一个可观察对象,稍后将其连接到Web套接字

  7. 7

    使用Angular JS,在更改路线时关闭Web套接字连接的正确方法是什么?

  8. 8

    如何每个Web套接字仅使用一个DB连接?

  9. 9

    使用Nginx将Web套接字连接重定向到新创建的Docker容器

  10. 10

    使用rxjs创建一个可观察对象,稍后将其连接到Web套接字

  11. 11

    使用 python3 套接字连接到 Web 服务器

  12. 12

    使用Unix套接字的JDBC MySQL连接

  13. 13

    无法使用javascript连接网络套接字

  14. 14

    使用asynctask android连接到套接字

  15. 15

    无法使用javascript连接网络套接字

  16. 16

    Python套接字重新连接

  17. 17

    使用Jasmine测试Web套接字

  18. 18

    如何开始使用Web套接字?

  19. 19

    使用套接字从Web Java获取图片

  20. 20

    使用Jasmine测试Web套接字

  21. 21

    如何开始使用Web套接字?

  22. 22

    使用sails.io.js在Ionic应用程序与Sails API之间建立Web套接字连接

  23. 23

    如何使用OS X的“ security add-trusted-cert”命令指定证书的策略约束?(用于SSL Web套接字连接)

  24. 24

    如何使用其 IP 地址从 Web 浏览器连接到 python 套接字服务器?

  25. 25

    使用全双工套接字通信进行SSL重新协商

  26. 26

    ZeroMQ:重新绑定套接字时使用地址错误

  27. 27

    ZeroMQ:重新绑定套接字时,地址使用中错误

  28. 28

    在哪里使用React中的Hooks定义需要从全局状态获取数据的套接字事件监听器

  29. 29

    python套接字如何使用相同的套接字连接正确重定向http / s请求?

热门标签

归档