React和Socket.io中的useEffect钩子触发了两次重新渲染

3
我正在使用React和socket构建实时聊天应用程序,以掌握基础知识。每当用户发送一条消息时,所有其他用户都会收到相同的消息两次。
我使用react-router来提供加入“页面”和聊天“页面”。
function App() {
  return (
    <div className="App">

      <Router>
        <Routes>
          <Route path="/" element={<Join />} />
          <Route path="/chat/:name" element={<Chat />} />
        </Routes>
      </Router>

    </div>
  );
}

在聊天页面中,消息是在用户之间呈现的。我正在使用useEffect hook,但我仍然难以完全理解它。
function Chat () {
  const [messages, addMessage] = useState([{
    username: "chatbot",
    time: new Date(),
    message: "Welcome to the chatroom "
  }]);

  const location = useLocation();
  const [currentUser, updateUser] = useState(() => {
    const user = location.pathname.split('/chat/')[1];
    socket.emit("join_chat", {
      username: user
    })
    return user;
  });

  useEffect(() => {
    socket.on("receive_message", (data) => {
      console.log('received message');
      addMessage(prevMessages => {
        return [...prevMessages, {
          username: data.username,
          time: new Date(),
          message: data.message
        }]
      })
    })
  }, [socket])

  const sendMessage = (e) => {
    e.preventDefault();
    socket.emit("send_message", {
      message: currentMessage,
      username: currentUser,
    });

每次发送消息时,接收方会运行两次控制台日志。
据我所知,这个钩子允许我们定义一些代码,每当DOM发生变化时都会运行,因此我不确定这个变化为什么会发生两次?
我也不确定为什么socket.on应该首先位于useEffect中。如果useEffect仅在重新渲染后立即调用,那么它如何通过套接字仍然接收到每条消息?
1个回答

2
根据您的描述,似乎您正在将应用程序渲染到 React.StrictMode 组件中,该组件运行某些生命周期方法和函数两次,以帮助检测意外/意想不到的副作用。
这个效果会运行两次,而意外/意想不到的副作用是代码添加了 两个 事件处理程序来处理 "receive_message" 事件。 useEffect 钩子缺少返回一个清除函数来取消订阅套接字/消息事件,当组件卸载或 socket 依赖项更改时。
useEffect(() => {
  const handleMessage = (data) => {
    console.log('received message');
    addMessage(prevMessages => {
      return [...prevMessages, {
        username: data.username,
        time: new Date(),
        message: data.message
      }]
    });
  };

  socket.on("receive_message", handleMessage);

  return () => {
    socket.off("receive_message", handleMessage);
  };
}, [socket]);

据我所了解,钩子允许我们定义一些代码,每当DOM发生更改时就会运行。 useEffect 钩子允许您定义一些代码,在组件由于任何原因重新渲染时作为副作用运行。React组件重新渲染的原因有:(1)本地状态已更新,(2) React属性已更新,(3)父组件重新渲染(出于任何相同的原因)。
我也不确定为什么 socket.on 应该首先存在于 useEffect 中。
在React组件中,“render”方法被视为纯函数。React函数组件的整个函数体都是“render”方法。订阅外部资源被认为是一个“不纯”的操作,即它是一个副作用,并且应该作为常规React组件生命周期的一部分进行订阅。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接