Я создаю свое первое веб-сокет-приложение с React, Socket.io и Express. Я пытался использовать это как возможность изучить API Hooks, и это оказывается немного сложнее.
Я устанавливаю sh прослушиватель на сокете в клиентском приложении внутри useEffect, которое запускается только после первого рендеринга (я передаю ему пустой массив в качестве второго аргумента). Слушатель работает, когда пользователь входит в систему из этого клиентского приложения, но при открытии второго окна и входе из другого соединения он не регистрируется снова для этого приложения. Второе окно имеет правильный массив пользователей с сервера.
Я ищу объяснение о , почему мое приложение ведет себя так. Я прочитал рекомендацию , что я должен использовать useReducer вместо useState в этом случае, но я не уверен, как это будет иметь значение, потому что console.log
в моем обратном вызове для прослушивателя событий на сокет все еще ничего не регистрирует. Даже в этом примере автор все еще объясняет, что состояние должно быть изменено и инициировано повторное рендеринг. И хотя я использовал пустой массив в качестве второго аргумента в useEffect, это не должно иметь значения, потому что там установлен прослушиватель событий.
Я избегал ситуаций, когда я пишу несколько useEffects, которые открывают и закрывают соединения, или перезаписывают несколько слушателей событий снова.
Я устанавливаю sh мое соединение вверху страницы:
import React, { useEffect, useState, useRef } from "react"
import socketIOClient from "socket.io-client";
import Layout from "../components/layout"
import Login from "../components/login"
//establish socket connection
const socket = socketIOClient("http://localhost:4001")
Я использую useState для создания здесь состояния:
///one is for the current user, the second is for the other users with already established connections
const [user, setUser] = useState("loading")
const [users, setUsers] = useState([])
И затем я использую мой первый хук useEffect для проверки вошедшего в систему пользователя в localStorage, а также для добавления прослушивателя событий в сокет:
useEffect(() => {
checkForLoggedInUser()
socket.on("backendUsers", (data) => {
setUsers(data)
console.log(data)
})
return () => socket.disconnect()
}, [])
Как уже упоминалось выше, прослушиватель работает отлично в первый раз, когда пользователь регистрируется в использовании формы или находится в localStorage. Он регистрируется на сервере, и сервер выдает «backendUsers», уведомляя, что обновление только что произошло. Но когда я добавляю другого пользователя в другое окно (в режиме инкогнито), слушатель не перехватывается снова, хотя при входе второго пользователя выдается «backendUsers». Второе окно содержит актуальный список пользователей, но в В первом окне я ожидаю обновленного массива пользователей, вошедших в консоль, а также повторного рендеринга, показывающего, что состояние обновлено и пользователи правильно перечислены в DOM.
Вот полный код моего интерфейса:
import React, { useEffect, useState, useRef }from "react"
import socketIOClient from "socket.io-client";
import Layout from "../components/layout"
import Login from "../components/login"
const socket = socketIOClient("http://localhost:4001")
const IndexPage = () => {
const [user, setUser] = useState("loading")
const [users, setUsers] = useState([])
useEffect(() => {
checkForLoggedInUser()
socket.on("backendUsers", (data) => {
setUsers(data)
console.log(data)
})
return () => socket.disconnect()
}, [])
useEffect(() => {
if(user !== null && user !== "loading"){
window.addEventListener('beforeunload', logoutUserFromServer)
}
}, [user])
//variables in useEffect are scoped to the moment they are executed. This is what makes useEffect work
//if you want access to variables that change, use useReducer
const logoutUserFromServer = () => {
socket.emit("logout", user)
window.removeEventListener('beforeunload', logoutUserFromServer)
}
const checkForLoggedInUser = () => {
const user = localStorage.getItem('websocketUser')
if (user){
loginUser(user)
} else {
setUser(null)
}
}
const loginUser = (user) => {
localStorage.setItem('websocketUser', user)
setUser(user)
socket.emit("login", user)
}
return (
<Layout>
<div style={{ textAlign: "center" }}>
{user == "loading" ? user : <Login user={user} loginUser={loginUser}/>}
<br/>
{users.length ? `The users are: ${users.join(", ")}` : "No users"}
</div>
</Layout>
);
}
export default IndexPage
А вот код на стороне сервера (здесь я использую Express. js). Основной слушатель, чтобы смотреть это login
:
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server);
let users = []
io.on("connection", socket => {
console.log("New client connected")
//emits "backendUsers" with the current users array anytime a user logs in from any client connection
socket.on("login", user => {
users.push(user)
socket.emit("backendUsers", users)
console.log(users)
})
socket.on("logout", user => {
users = users.filter(name => name!== user)
console.log(`${user} logged out`, users)
})
socket.on("disconnect", () => console.log("Client disconnected"));
});
server.listen(port, () => console.log(`Listening on port ${port}`));
};