» React创建在线聊天软件 Web Chat App 前端 » 2. 开发 » 2.3 Socket.IO 连接

Socket.IO 连接

Socket.IO 有两大组成部分:

  • 服务端,与 Node.JS HTTP Server 集成;(socket.io 包)
  • 客户端,在浏览器中加载运行。(socket.io-client 包)

在这个前端项目中,我们只使用客户端包。

npm i socket.io-client

登录窗口

添加 src/components/login-form.jsx:

import React, { useState } from "react";
import "./Login.css";

const emojis = [
  "🐨", // Koala
  "🐒", // Monkey
  "🐼", // Panda
  "🐘", // Elephant
  "🐬", // Dolphin
  "🐻", // Bear
  "🐶", // Dog
  "🐱", // Cat
  "🐭", // Mouse
  "🐰", // Rabbit
  "🦊", // Fox
  "🦁", // Lion
  "🐯", // Tiger
  "🐮", // Cow
  "🐗", // Boar
  "🐴", // Horse
  "🐑", // Sheep
  "🐺", // Wolf
  "🐹", // Hamster
  "🐻‍❄️", // Polar bear
  "🦝", // Raccoon
  "🐧", // Penguin
  "🦥", // Sloth
  "🦘", // Kangaroo
  "🦄", // Unicorn
];
const LoginForm = (props) => {
  const [emoji, setEmoji] = useState("");
  const [name, setName] = useState("");
  const login = () => {
    if (!emoji) {
      alert("Emoji needed.");
      return;
    }
    if (!name) {
      alert("Name needed.");
      return;
    }
    props.callback(emoji, name);
  };
  return (
    <div className="login-form">
      <div>
        <div className="form-row">
          <label>Emoji</label>
          <select
            className="input-box"
            value={emoji}
            onChange={(e) => setEmoji(e.target.value)}
          >
            <option value="" disabled>
              Pick Your Emoji
            </option>
            {emojis.map((e, i) => (
              <option className="input-option" key={i} value={e}>
                {e}
              </option>
            ))}
          </select>
        </div>
        <div className="form-row">
          <label>Name</label>
          <input
            className="input-box"
            value={name}
            onChange={(e) => setName(e.target.value)}
            onKeyUp={(e) => {
              if (e.key === "Enter") {
                login();
              }
            }}
          />
        </div>
      </div>
      <div className="buttons">
        <button className="send-btn" onClick={login}>
          Login
        </button>
      </div>
    </div>
  );
};

export default LoginForm;

添加 src/components/Login.css:

@import url(../vars.css);

.login-form {
  box-sizing: border-box;
  background-color: rgba(255, 255, 255, 0.2);
  width: 20em;
  margin: 1em auto;
  border-radius: 0.5rem;
  padding: 1em;
}

.form-row {
  display: flex;
  justify-content: space-between;
  margin-top: 1em;

  label {
    width: 5em;
    margin-right: 1em;
  }
}

.input-box {
  border: none;
  font-size: large;
  width: 100%;
  border-bottom: solid 2px var(--theme-color);
}

.input-box:focus {
  outline: solid 1px var(--theme-color);
}

.input-option {
  text-align: center;
}

.buttons {
  margin-top: 2em;
}

连接到 Socket.IO 服务器

将登录窗口置入 src/App.js:

@@ -1,9 +1,16 @@
+import { useEffect, useState } from "react";
 import "./App.css";
 import Contact from "./components/contact";
 import Message from "./components/message";
 import TitleBar from "./components/ttile-bar";
+import LoginForm from "./components/login-form";
+import clsx from "clsx";
+import io from "socket.io-client";
 
 function App() {
+  const [logged, setLogged] = useState(false);
+  const [user, setUser] = useState({ emoji: "", name: "" });
+  const [conn, setConn] = useState(null);
   const messages = [
     {
       username: "Alice",
@@ -40,50 +47,74 @@ function App() {
       message: "How's your assignment? I didn't do much yesterday",
     },
   ];
+  useEffect(() => {
+    const socket = io("http://localhost:4000");
+    socket.on("error", (error) => {
+      console.error("Socket error:", error);
+    });
+    setConn(socket);
+  }, []);
+
+  const login = (emoji, name) => {
+    setUser({ emoji, name });
+    setLogged(true);
+  };
   return (
     <div className="app">
-      <h1 className="app-name">Literank Web Chat</h1>
-      <div className="segments">
-        <span className="segment left-seg picked">Chat</span>
-        <span className="segment right-seg">Groups</span>
-      </div>
-      <div className="card">
-        <div className="contacts">
-          {contacts.map((e) => (
-            <Contact
-              username={e.username}
-              message={e.message}
-              isOffline={e.username.includes("Emily")}
-            />
-          ))}
-        </div>
-        <div className="main">
-          <TitleBar username={contacts[0].username} />
-          <div className="messages">
-            {messages.map((e) => (
-              <Message
-                username={e.username}
-                message={e.message}
-                isSelf={e.username === "Bob"}
-              />
-            ))}
+      <h1 className={clsx("app-name", { "center-name": !logged })}>
+        Literank Web Chat
+      </h1>
+      {!logged ? (
+        <LoginForm callback={login} />
+      ) : (
+        <>
+          <div className="segments">
+            <span className="segment left-seg picked">Chat</span>
+            <span className="segment right-seg">Groups</span>
+          </div>
+          <div className="card">
+            <div className="contacts">
+              {contacts.map((e) => (
+                <Contact
+                  username={e.username}
+                  message={e.message}
+                  isOffline={e.username.includes("Emily")}
+                />
+              ))}
+            </div>
+            <div className="main">
+              <TitleBar username={contacts[0].username} />
+              <div className="messages">
+                {messages.map((e) => (
+                  <Message
+                    username={e.username}
+                    message={e.message}
+                    isSelf={e.username === "Bob"}
+                  />
+                ))}
+              </div>
+              <div className="edit">
+                <textarea className="edit-box" placeholder="Type here" />
+                <div className="buttons">
+                  <button className="send-btn">Send</button>
+                  <span className="tip">Ctrl+Enter to send</span>
+                </div>
+              </div>
+            </div>
           </div>
-          <div className="edit">
-            <textarea className="edit-box" placeholder="Type here" />
-            <div className="buttons">
-              <button className="send-btn">Send</button>
-              <span className="tip">Ctrl+Enter to send</span>
+          <div className="status">
+            <span>
+              {user.emoji} {user.name}
+            </span>
+            <div className="connection-status">
+              <span
+                className={clsx("dot", { connected: conn?.connected })}
+              ></span>
+              <span>{conn?.connected ? "Connected" : "Disconnected"}</span>
             </div>
           </div>
-        </div>
-      </div>
-      <div className="status">
-        <span>🐱 Bob</span>
-        <div className="connection-status">
-          <span className="dot connected"></span>
-          <span>Connected</span>
-        </div>
-      </div>
+        </>
+      )}
     </div>
   );
 }

const socket = io("http://localhost:4000") 这一句就可以连接到 socket.io 服务器。

你需要一个运行在 4000 端口的 socket.io 服务器。
可查看 Node.js:使用Socket.IO创建在线聊天应用 或其他语言实现的后端服务器。

调整风格,src/App.css:

@@ -16,6 +16,10 @@ body {
   text-align: left;
 }
 
+.center-name {
+  text-align: center;
+}
+
 .segments {
   margin-top: 1rem;
 }
@@ -124,7 +128,7 @@ body {
   width: 10px;
   height: 10px;
   border-radius: 5px;
-  background-color: white;
+  background-color: lightgray;
   margin-right: 5px;
 }