0

我正在使用 React、Socket.io 和 Express 构建我的第一个 websocket 应用程序。我也一直试图以此为契机学习 Hooks API,但事实证明这有点棘手。

我在仅在第一次渲染后运行的 useEffect 内的客户端应用程序中的套接字上建立了一个侦听器(我将一个空数组作为第二个参数传递给它)。当用户从该客户端应用程序登录时,侦听器工作,但是当打开第二个窗口并从不同的连接登录时,它不会再次为该应用程序注册。第二个窗口具有来自服务器的正确用户数组。

我正在寻找有关为什么我的应用程序会以这种方式运行的解释。我已经阅读了一个建议,在这种情况下我应该使用 useReducer 而不是 useState ,但我不确定这会有什么不同,因为console.log在我的套接字上事件侦听器的回调中仍然没有注册任何东西。即使在示例中,作者仍在解释应该更改状态并触发重新渲染。尽管我在 useEffect 中使用了一个空数组作为第二个参数,但这并不重要,因为事件侦听器是在那里建立的。

我已经避免了编写多个打开和关闭连接或重新编写多个事件侦听器的 useEffects 的情况。

我在页面顶部建立连接:

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}`));

};

4

2 回答 2

1

使用 and[]作为第二个参数的 useEffect 等效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文档以进一步阅读

于 2020-04-01T20:57:04.957 回答
1

我需要在后端使用 io 变量而不是 socket 变量!socket.emit 只发送到接收客户端。io.emit 发送到所有连接。

于 2020-04-01T23:55:38.720 回答