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