在反应中收到消息后立即未定义Websocket

Posted

技术标签:

【中文标题】在反应中收到消息后立即未定义Websocket【英文标题】:Websocket is undefined immediately after receiving message in react 【发布时间】:2021-09-20 05:31:21 【问题描述】:

我在反应中使用 websocket。这是组件的代码:

import React,  useState, useEffect  from "react";

export default function Component(props) 
  const [socket, setSocket] = useState();

  const parseMessage = (msg) => 
    if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket. 
  ;

  const sendMessage = (msg) => socket.send(msg); // error at this line

  useEffect(() => 
    const socket = new WebSocket("wss://ws.ifelse.io/");
    socket.addEventListener("message", ( data ) => 
      if (socket) parseMessage(data);
    );
    setSocket(socket);
  , []);

  const sendMsg = () => 
    socket.send("test");
  ;

  return <button onClick=() => sendMsg("clicked")>send msg</button>;


我收到此错误:TypeError: Cannot read properties of undefined (reading 'send') 在标记的行。 WebSocket 只是一个回显服务器,它会发回您发送给它的相同内容。

如果我将 socket.send 包装在 try-catch 块中,我仍然可以使用按钮从 WebSocket 发送和接收消息,但错误仍然出现在 sendMessage 中的那一行。

很明显,套接字变量不是未定义的,因为我能够在错误发生之前和之后发送和接收消息。

所以我的问题是为什么套接字变量仅在收到消息后的短暂时间内未定义,以及解决此问题的方法是什么。

【问题讨论】:

【参考方案1】:

更好的解决方案是在外部初始化套接字

import React,  useState, useEffect  from "react";

const socket = new WebSocket("wss://ws.ifelse.io/")

export default function Component(props) 
  const ws = useRef(socket)
  
  useEffect(() => 
    ws.current?.addEventListener("message", ( data ) => 
      parseMessage(data);
    );
    return () => 
      ws.current?.removeAllListeners()
    
  , [])

  const parseMessage = (msg) => 
    if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket. 
  ;

  const sendMessage = (msg) => ws.current?.send(msg);


  const sendMsg = () => 
    ws.current?.send("test");
  ;

  return <button onClick=() => sendMsg("clicked")>send msg</button>;

【讨论】:

【参考方案2】:

useEffect 只运行一次,在那一刻,socket 仍未定义。当效果运行时,函数sendMessage 引用未定义的socket。当使用setSocket设置socket时,组件会重新渲染,但是sendMessage的新实例(现在引用现有的socket)将永远不会被使用,因为效果不会再次运行。

在这种情况下最好使用 ref。

export default function Component(props) 
  const socket = useRef();

  const sendMessage = (msg) => socket.current.send(msg); 

  useEffect(() => 
    socket.current = new WebSocket("wss://ws.ifelse.io/");
    socket.current.addEventListener("message", ( data ) => 
      parseMessage(data);
    );
    return () => 
       ... release resources here
    
  , []);
  
  ...

【讨论】:

是的,这就是问题所在。我一直忘记重新渲染时会重新初始化函数。【参考方案3】:

当您使用useState 时,您会创建一个具有更新状态函数的状态变量。您还在useState(initialState) 中提供状态变量的初始值。

在你的情况下,你没有传递任何东西(即你告诉 React 它是undefined,这就是为什么在这一行:

  const sendMessage = (msg) => socket.send(msg); // error at this line

您的代码不能使用socket.send,因为套接字是undefined

您的useEffect 在返回函数之后运行,然后创建套接字实例并将其设置为套接字状态变量。

您需要确保仅在创建套接字后通过套接字发送消息。

有关useEffect 的更多信息,您可以阅读这篇文章: https://jayendra.xyz/2019/06/17/how-useeffect-works/

【讨论】:

我知道情况并非如此,因为我能够在错误消息实例之间的套接字上发送和接收消息。换句话说,我只是在套接字初始化之后才发送消息。 尝试将此行:const sendMessage = (msg) =&gt; socket.send(msg); 置于 if 条件中。 if (socket) const sendMessage = (msg) =&gt; socket.send(msg); 并检查您是否仍然遇到相同的错误。 不,如果我检查套接字是否可用,我不会收到该错误。 我认为这就是重点。甚至您的错误也表明:TypeError: Cannot read properties of undefined (reading 'send')。当//error at line 代码被执行时,socket 还不可用。您需要在套接字初始化后执行此操作。 使用套接字发送消息后发生错误。所以我认为可以安全地假设套接字在那个时候被初始化。【参考方案4】:

需要先等待socket建立,然后在open事件中使用setSocket设置socket。您现在执行的方式期望服务器发送第一条消息,然后您设置套接字,如果服务器在您单击按钮之前未发送消息,则套接字实例未设置为您的状态。 改为这样做:

socket.addEventListener('open', function (event) 
    setSocket(socket);
);

这是完整的代码

import React,  useState, useEffect  from "react";

export default function Component(props) 
  const [socket, setSocket] = useState();
  const [disabled, setButtonDisabled] = useState(true);

  const parseMessage = (msg) => 
    if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket. 
  ;

  const sendMessage = (msg) => socket.send(msg); // error at this line

  useEffect(() => 
    const socket = new WebSocket("wss://ws.ifelse.io/");

    socket.addEventListener('open', function (event) 
     setSocket(socket);
     setButtonDisabled(false); 
    ); 

    socket.addEventListener("message", ( data ) => 
      if (data) parseMessage(data);
    );

    return ()=>
      socket.close();
    ;
  , []);

  const sendMsg = () => 
    if(socket) socket.send("test");
  ;

  return <button disabled=disabled onClick=() => sendMsg("clicked")>send msg</button>;


【讨论】:

这里有更多关于 WebSockets 的信息:developer.mozilla.org/en-US/docs/Web/API/WebSocket 这有同样的问题 另外,为什么我希望服务器发送第一条消息?

以上是关于在反应中收到消息后立即未定义Websocket的主要内容,如果未能解决你的问题,请参考以下文章

升级依赖项时反应“TypeError:路径必须是字符串。收到未定义”

VueJS路由器查看反应道具未定义

Spring(Grails)WebSocket 立即记录错误:SubProtocolWebSocketHandler - *** ms 后未收到消息

反应:作为道具的数组显示为未定义

反应原生 html postMessage 无法到达 WebView

为啥在我的反应形式中此 FormArray 更改后,我从 JSON 文件中检索对象时收到此错误消息?