在 ReactJS 和 ExpressJS 中使用 Socket.io 时浏览器挂起

Browsers hangs while using Socket.io with ReactJS and ExpressJS

问题:

我正在开发一个聊天应用程序。当我发送超过 9-10 个请求时,浏览器变慢并最终挂起。刷新页面,一切恢复正常。 我搜索了 socket.io 文档,但无法找到有关此事的任何解决方案。

代码:

这是我的后端 Express.JS 代码:

index.js

const express = require("express");
const { addUser, getUser, deleteUser, getAllUsers } = require("./users_api");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
  },
});

const port = 5000 || process.env.PORT;

io.on("connection", (socket) => {
  socket.on("join", ({ name, room }, callback) => {
    const { err, user } = addUser({ id: socket.id, name, room });
    if (err) return callback(err);
    if (user) {
      socket.emit("message", {
        user: "admin",
        text: `${user.name} has entered the room ${user.room}.`,
      });
      socket.broadcast.to(user.room).emit("message", {
        user: "admin",
        text: `${user.name} has joined the room.`,
      });
      socket.join(user.room);
      io.to(user.room).emit("users", {
        room: user.room,
        users: getAllUsers(user.room),
      });

      callback();
    }
  });

  socket.on("client_message", (msg) => {
    const user = getUser(socket.id);
    if (user) io.to(user.room).emit("message", { user: user.name, text: msg });
  });

  socket.on("disconnect", () => {
    console.log(6);
    const user = deleteUser(socket.id);
    if (user) {
      io.to(user.room).emit("message", {
        user: "admin",
        text: `${user.name} has left the room.`,
      });
      io.to(user.room).emit("users", {
        room: user.room,
        users: getAllUsers(user.room),
      });
    }
  });
});

server.listen(port, () => console.log(`Server started at port ${port}.`));

users_api.js:

const current_users = [];

const addUser = ({ id, name, room }) => {
  name = name.trim().toLowerCase().split(" ").join("_");
  room = room.trim().toLowerCase();

  const exist_user = current_users.find(
    (user) => name === user.name && room === user.room
  );

  if (exist_user)
    return {
      err: "User with this name already exists.",
    };

  current_users.push({ id, name, room });
  return { user: { id, name, room } };
};

const deleteUser = (id) => {
  const index = current_users.findIndex((user) => user.id === id);
  if (index !== -1) return current_users.splice(index, 1)[0];
};

const getUser = (id) => current_users.find((user) => user.id === id);

const getAllUsers = (room) =>
  current_users.filter((user) => user.room === room);

module.exports = { addUser, deleteUser, getUser, getAllUsers };

前端代码:

import io from "socket.io-client";
import React, { useEffect, useRef, useState } from "react";
import queryString from "query-string";
import "./ChatPage.css";

const END_POINT = "http://localhost:5000";

const ChatPage = (props) => {
  const [message, setMessage] = useState("");
  const [messages, setMessages] = useState([]);
  const [users, setUsers] = useState([]);
  const socket = useRef(null);

  const handleSubmit = () => {
    socket.current.emit("client_message", message);
    setMessage("");
  };

  useEffect(() => {
    const { name, room } = queryString.parse(props.location.search);
    socket.current = io(END_POINT);
    socket.current.emit("join", { name, room }, (error) => {
      if (error) alert(error);
    });

    return () => {
      socket.current.disconnect();
      socket.current.off();
    };
  }, [props.location.search, END_POINT]);

  useEffect(() => {
    socket.current.on("message", (msg) => {
      setMessages([...messages, msg]);
    });
    socket.current.on("users", ({ users }) => {
      setUsers(users);
    });
  }, [messages.length]);

  return (
    <div className="chat-room-container">
      <div className="chat-room-left">
        <ul>
          {messages.map((message, i) => (
            <li key={i}>
              {message.name}
              {message.text}
            </li>
          ))}
        </ul>
        <input
          type="text"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
        />
        <button type="button" onClick={handleSubmit}>
          Submit
        </button>
      </div>
      <div className="chat-room-right">
        <ul>
          {users.map((user, i) => (
            <li key={i}>{user.name}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default ChatPage;

我不明白哪里出了问题,请帮忙。

您在每个 useEffect 上创建新的套接字连接,因此在十条消息之后,您有十个连接。

socket = io(END_POINT);

我将创建的套接字连接存储在 useRef 中 - 因此如果再次创建它,它会被新的覆盖 - 并且不会重复。

const socketConection = useRef(null)

useEffect(() => {
  socketConnection.current = io(END_POINT)
}, [deps])

当然,在后面的所有使用中你都必须使用socketConection.current

不要在事件或广播中发送消息,而是尝试将消息列表发送给特定用户或所有用户列表,他们的消息属于同一房间。这将减少前端渲染。

例如, 您的前端代码将更改为

useEffect(() => {
    socket.current.on("message", (msgList) => {
      setMessages(msgList);
    });
    socket.current.on("users", ({ users }) => {
      setUsers(users);
    });
  }, [messages.length]);

此外,如果不同的房间会出现过滤消息的问题。所以要解决这个问题有两种可能的方法:

  1. 当您从服务器发送过滤器数据时。
  2. 根据用户名和房间名过滤客户端中的数据。

由于此解决方案不是一个好的做法,因为每次都会出现整个消息列表并对其应用过滤器会增加时间复杂度。但是你可以通过在服务器上对数据状态进行适当的结构来在一定程度上减少。

我想这会对你有所帮助。