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

Websocket is undefined immediately after receiving message in react

我在 React 中使用 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 中的那一行。

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

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

当您使用 useState 时,您创建了一个具有更新状态函数的状态变量。您还在 useState(initialState).

中提供状态变量的初始值

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

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

您的代码无法使用 socket.send 因为套接字是 undefined.

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

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

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

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

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

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

在这种情况下最好使用 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
    }
  }, []);
  
  ...
}

需要先等待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>;
}