React + Express:对于某些用户来说,服务器中的数据似乎没有传递到前端
我做了一个网页游戏,用户可以创建房间并让其他用户加入。每当用户加入房间时,应该向加入用户本人发送一条欢迎消息,并向房间中的其他用户发送“xxx 已加入”消息。房间页面左侧的个人资料图片应更新以显示房间中每个用户的个人资料图片。
我用两个账号测试了,user1创建房间时,一切正常。但是,当 user2 加入房间时,user1 页面中的所有内容都已正确更新,但 user2 页面中没有任何内容更新(没有欢迎消息、没有个人资料图片等),并且
console.log("roomData", roomData)
甚至没有在 user2 的页面中记录任何内容。
这是我的后端代码index.js:
// utils
const process = require("process");
const cors = require("cors");
const socket = require("socket.io");
// imports
const verifyToken = require("./middleware/VerifyToken");
const { setUsername, join, addRoom, getRoom, deleteRoom, getCurrentUser, getUsersInRoom, leave } = require("./middleware/roomManagement");
const { newGame, updateGame, removeGame } = require("./middleware/gameManagement");
require("dotenv").config();
// express app
const express = require("express");
const app = express();
const portNumber = process.env.PORT_NUMBER || 8080;
// middleware
app.use(express());
app.use(express.urlencoded({ extended: false }));
app.use(cors());
app.use(verifyToken);
var server = app.listen(portNumber, (e) => {
if(e) {
console.log("Failed to start the server with error message: " + e);
}else {
console.log(`Web server started and running at http://localhost:${portNumber}`);
}
});
const io = socket(server);
io.on("connection", (socket) => {
console.log("socket connected");
socket.on("changeName", (username) => {
setUsername(socket.id, username);
});
socket.on("joinRoom", ({userId, roomId, username, isHost}) => {
let roomData = getRoom(roomId);
console.log("outterRoomData", roomData);
if(roomData && roomData.inGame) {
console.log("in if");
socket.emit("roomData", roomData);
return;
}else {
console.log("in else");
// create user
const curUser = join(socket.id, userId, roomId, username, isHost);
socket.join(curUser.roomId);
// welcome message
socket.emit("message", {
username: curUser.username,
text: `Welcome, ${curUser.username}!`,
isCipher: false
});
// show that the user joined
socket.broadcast.to(curUser.roomId).emit("message", {
username: curUser.username,
text: `${curUser.username} has joined the room`,
isCipher: false
});
const users = getUsersInRoom(curUser.roomId);
if(roomData) {
// update roomData for room if it alreade exist
roomData.users = users;
}else {
// create room if room is not already exist
const hostId = curUser.userId;
const status = false;
roomData = addRoom(roomId, users, hostId, status);
}
console.log("innerRoomData", roomData);
// send roomData to client side
io.to(curUser.roomId).emit("roomData", roomData);
}
});
// start game
socket.on("startGame", (roomId, lastLose) => {
let room = getRoom(roomId);
if(room) {
// set roomData.inGame to true and send it to client side
room.inGame = true;
io.to(roomId).emit("roomData", room);
const game = newGame(roomId, lastLose);
io.to(roomId).emit("game", game);
}
});
// update game status
socket.on("game", (game) => {
const curUser = getCurrentUser(socket.id);
const updatedGame = updateGame(curUser.roomId, game);
io.to(curUser.roomId).emit("game", updatedGame);
});
socket.on("endGame", (roomId) => {
let room = getRoom(roomId);
if(room) {
// set roomData.inGame to false and sned it to client side
room.inGame = false;
io.to(roomId).emit("roomData", room);
// delete game in data
removeGame(roomId);
}
});
// user sending message
socket.on("chat", (text) => {
const curUser = getCurrentUser(socket.id);
io.to(curUser.roomId).emit("message", {
username: curUser.username,
text: text,
isCipher: true
});
});
socket.on("called", (name) => {
const curUser = getCurrentUser(socket.id);
io.to(curUser.roomId).emit("called", name);
});
// user leave the room
socket.on("leave", () => {
const curUser = leave(socket.id);
if(curUser) {
socket.leave(curUser.roomId);
// message shows user leaving
io.to(curUser.roomId).emit("message", {
username: curUser.username,
text: `${curUser.username} has left the room`
});
const users = getUsersInRoom(curUser.roomId);
if(users.length == 0) {
// delete room if there is no user in it
deleteRoom(curUser.roomId);
}else if(users.length > 0) {
const room = getRoom(curUser.roomId);
// set new host if host left
if(room.hostId === curUser.userId) {
room.hostId = users[0].userId;
users[0].isHost = true;
}
// update users in room
room.users = users;
io.to(curUser.roomId).emit("roomData", room);
}
}
});
});
我在前端有 Room.js,如下所示:
import { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { useParams, useNavigate } from "react-router-dom";
import { ref, getDownloadURL } from "firebase/storage";
import { ClipboardCopyIcon } from "@heroicons/react/outline";
import { process } from "../../services/store/action";
import { to_Decrypt, to_Encrypt } from "../../utils/aes";
import { useAuth } from "../../contexts/AuthContext";
import { useAutoScroll } from "../../utils/autoScroll";
import { storage } from "../../config/firebase";
import { GameArea } from "./GameArea";
export default function Room({ socket }) {
const navigate = useNavigate();
const [text, setText] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const [userpics, setUserpics] = useState({});
const [hostId, setHostId] = useState("");
const [inGame, setInGame] = useState();
const { roomId } = useParams();
const { setError } = useAuth();
const dispatch = useDispatch();
const userRef = useAutoScroll(userpics);
const chatRef = useAutoScroll(messages);
// useEffect(() => {
// // handle messages
// const dispatchProcess = (encrypt, msg, cipher) => {
// dispatch(process(encrypt, msg, cipher));
// };
// socket.on("message", (msgdata) => {
// // decypt the message
// const ans = to_Decrypt(msgdata.text, msgdata.isCipher);
// dispatchProcess(false, ans, msgdata.text);
// setMessages((prevMessages) => [
// ...prevMessages,
// {username: msgdata.username, text: ans}
// ]);
// });
// // display users
// socket.on("roomData", async (roomData) => {
// console.log("roomData", roomData);
// setHostId(roomData.hostId);
// setInGame(roomData.inGame);
// let tempPics = new Map();
// let tempUsers = [];
// await Promise.all(
// roomData.users.map(async (user) => {
// const picRef = ref(storage, 'profilePic/' + user.userId + '.jpg');
// try{
// const url = await getDownloadURL(picRef);
// tempPics.set(user.userId, url);
// tempUsers.push(user);
// }catch (e) {
// console.log(e.message);
// }
// })
// );
// setUserpics(tempPics);
// setUsers(tempUsers);
// });
// return () => {
// socket.off("message");
// socket.off("roomData");
// }
// }, [dispatch, messages, socket]);
useEffect(() => {
const dispatchProcess = (encrypt, msg, cipher) => {
dispatch(process(encrypt, msg, cipher));
};
const handleMessage = (msgdata) => {
const ans = to_Decrypt(msgdata.text, msgdata.isCipher);
dispatchProcess(false, ans, msgdata.text);
setMessages((prevMessages) => [
...prevMessages,
{ username: msgdata.username, text: ans },
]);
};
const handleRoomData = async (roomData) => {
console.log("roomData", roomData);
setHostId(roomData.hostId);
setInGame(roomData.inGame);
let tempPics = new Map();
let tempUsers = [];
await Promise.all(
roomData.users.map(async (user) => {
const picRef = ref(storage, 'profilePic/' + user.userId + '.jpg');
try {
const url = await getDownloadURL(picRef);
tempPics.set(user.userId, url);
tempUsers.push(user);
} catch (e) {
console.log(e.message);
}
})
);
setUserpics(tempPics);
setUsers(tempUsers);
};
socket.on("message", handleMessage);
socket.on("roomData", handleRoomData);
return () => {
socket.off("message", handleMessage);
socket.off("roomData", handleRoomData);
};
}, [dispatch, socket]);
const sendData = () => {
if(text !== "") {
// encrypt the message
const ans = to_Encrypt(text);
socket.emit("chat", ans);
setText("");
}
};
const copyRoomId = () => {
navigator.clipboard.writeText(roomId);
}
const leaveRoom = () => {
socket.on("roomData", (roomData) => {
setInGame(roomData.inGame);
});
if(inGame) {
setError("Game in process, please do not leave.");
}else {
socket.emit("leave");
navigate("/");
}
}
const userIds = users.map(user => user.userId);
return(
<div>
<div id="roomIdDisplay">Room ID: {roomId}
<button onClick={copyRoomId}><ClipboardCopyIcon id="copyIcon" /></button>
</div>
<button className="logoutbutton" onClick={leaveRoom}>Leave</button>
<div id="roomUsers" ref={userRef}>
<div id="usersContent">
{userIds.map((userId, index) => {
const url = userpics.get(userId);
return (
<div key={index}>
<img src={url} alt="profilepic" className="roomProfilepic" />
</div>
);
})}
</div>
</div>
<div>
<GameArea socket={socket} roomId={roomId} users={users} host={hostId} />
</div>
<div id="chatBox">
<div id="chatHistory" ref={chatRef}>
<div id="chatContent">
{messages.map((i, index) => {
return (
<div key={index} className="message">
<p>{i.username}: {i.text}</p>
</div>
);
})}
</div>
</div>
<div id="sendInput">
<input
id="msgInput"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyPress={(e) => {
if(e.key === "Enter") {
sendData();
}
}}
/>
<button onClick={sendData}>Send</button>
</div>
</div>
</div>
);
}
我还确保套接字正确传递到前端 App.js 中的 Room.js,如下所示:
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import React from "react";
import io from "socket.io-client";
import './index.css';
import { AuthProvider } from "./contexts/AuthContext";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import User from "./components/account/User";
import Profile from "./components/account/Profile";
import Room from "./components/games/Room";
import JoinRoom from "./components/games/JoinRoom";
import Background from "./components/layouts/Background";
import ErrorMessage from "./components/layouts/ErrorMessage";
import WithPrivateRoute from "./utils/WithPrivateRoute";
const socket = io.connect("/");
function App() {
const generateRoomId = () => {
let S4 = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() +S4();
}
return (
<AuthProvider>
<Router>
<Background />
<ErrorMessage />
<Routes>
<Route exact path="/register" element={<Register />} />
<Route exact path="/login" element={<Login />} />
<Route exact path="/profile" element={
<WithPrivateRoute>
<Profile socket={socket}/>
</WithPrivateRoute>
} />
<Route exact path="/" element={
<WithPrivateRoute>
<User generateRoomId={generateRoomId} socket={socket}/>
</WithPrivateRoute>
} />
<Route exact path="/join" element={
<WithPrivateRoute>
<JoinRoom socket={socket}/>
</WithPrivateRoute>
} />
<Route path="room/:roomId" element={
<WithPrivateRoute>
<Room socket={socket} />
</WithPrivateRoute>
}/>
</Routes>
</Router>
</AuthProvider>
);
}
export default App;
有趣的事实是,当用户1离开房间时,用户2的页面就可以正确更新。
回答如下: