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