0

React、Socket.io、および Express を使用して、最初の websocket アプリケーションを構築しています。私はこれをフック API を学ぶ機会として利用しようとしてきましたが、少しトリッキーであることがわかりました。

最初のレンダリング後にのみ実行される useEffect 内のクライアント アプリのソケットにリスナーを確立します (2 番目の引数として空の配列を渡します)。リスナーは、ユーザーがこのクライアント アプリケーションからログインすると機能しますが、2 番目のウィンドウを開いて別の接続からログインすると、そのアプリケーションに再登録されません。2 番目のウィンドウには、サーバーからのユーザーの正しい配列が表示されます。

アプリがこのように動作する理由についての説明を探しています。この場合、useState の代わりに useReducer を使用する必要があるという推奨事項を読みましたconsole.logが、ソケットのイベント リスナーのコールバックがまだ何も登録していないため、どのように違いが生じるかわかりません。この例でも、作成者は、状態を変更して再レンダリングをトリガーする必要があることを説明しています。また、useEffect の 2 番目の引数として空の配列を使用しましたが、イベント リスナーがそこに確立されているため、問題にはなりません。

接続を開いたり閉じたりする複数の 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」を発行して、更新が発生したことを通知します。しかし、別の (シークレット) ウィンドウに別のユーザーを追加すると、2 番目のユーザーがログインしたときに「backendUsers」が出力されますが、リスナーは再びキャッチされません。2 番目のウィンドウにはユーザーの最新のリストがありますが、最初のウィンドウでは、コンソールにログインしたユーザーの更新された配列と再レンダリングが期待されます。これは、状態が更新され、ユーザーが 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

ソケット変数の代わりに、バックエンドで io 変数を使用する必要がありました! socket.emit は、受信クライアントにのみ送信します。io.emit はすべての接続に送信します。

于 2020-04-01T23:55:38.720 に答える
1

および[]を 2 番目の引数として使用する 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()
}

したがって、このコードは、コンポーネントがマウントされたときに 1 回だけ実行されます。それが望ましくない場合は、2 番目の配列引数にいくつかの依存関係を追加して依存関係が変更されたときに実行するか、完全に削除してすべてのレンダリング後に実行する必要があります。詳細については、useEffectのドキュメントを参照してください。

于 2020-04-01T20:57:04.957 に答える