Почему мой слушатель websocket срабатывает только один раз, когда объявлен в useEffect с пустым массивом в качестве второго аргумента? - PullRequest
0 голосов
/ 01 апреля 2020

Я создаю свое первое веб-сокет-приложение с 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}`));

};

Ответы [ 2 ]

0 голосов
/ 02 апреля 2020

Мне нужно было использовать переменную io в бэкэнде вместо переменной сокета! socket.emit отправляет только получающему клиенту. io.emit отправляет всем соединениям.

0 голосов
/ 01 апреля 2020

useEffect with и [] в качестве второго аргумента эквивалентны componentDidMount() в компонентах на основе классов; поэтому этот код

useEffect(() => {
  checkForLoggedInUser()
  socket.on("backendUsers", (data) => {
    setUsers(data)
      console.log(data)
    })
  return () => socket.disconnect()
}, [])

примерно такой же, как этот

componentDidMount() {
 checkForLoggedInUser()
 socket.on("backendUsers", (data) => {
   setUsers(data)
   console.log(data)
 })
}

componentWillUnmount(){
  socket.disconnect()
}

, поэтому этот код будет выполняться только один раз, когда компонент смонтирован; если это не то, что вам нужно, вы должны добавить некоторые зависимости к этому второму аргументу массива для запуска при изменении зависимостей или полностью удалить его для запуска после каждого рендеринга. проверьте документацию useEffect для дальнейшего чтения

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...