💬 使用 Websockets、Novel 和 Clerk 建立实时聊天 🚀🚀
TL;DR
TL;DR
在本教程中,您将学习如何构建聊天应用程序。
议程🔥:
- 使用 React.js、Node.js 和 Websockets 创建帐户并发送实时消息。
- 与 Clerk 建立身份验证。
- 为与 Novel 的聊天添加富文本编辑器。
Novu:开源通知基础设施🚀
简单介绍一下我们。Novu 是一个开源通知基础设施。我们主要负责管理所有产品通知。通知可以是应用内通知(类似开发者社区里的铃铛图标)、电子邮件、短信等等。
https://github.com/novuhq/novu
让我们开始设置吧🚀
Socket.io是一个流行的 JavaScript 库,它允许我们在 Web 浏览器和 Node.js 服务器之间建立实时双向通信。它是一个高性能且可靠的库,经过优化,能够以最小的延迟处理大量数据。
在这里,您将学习如何将 Socket.io 添加到 React 和 Node.js 应用程序并连接两个开发服务器进行实时通信。
如下所示为 Web 应用程序创建一个文件夹。
mkdir chat-app
cd chat-app
mkdir client server
通过终端导航到客户端文件夹并使用 Vite创建一个新的 React.js 项目。
npm create vite@latest
安装 Socket.io 客户端 API 和 React Router。React Router 是一个 JavaScript 库,它使我们能够在 React 应用程序中的页面之间导航。
npm install socket.io-client react-router-dom
从 React 应用中删除多余的文件(例如徽标和测试文件),并更新App.jsx
文件以显示“Hello World”,如下所示。
function App() {
return (
<div>
<p>Hello World!</p>
</div>
);
}
将项目样式所需的CSS 文件复制 到src/index.css
文件中。
将 React 应用连接到 Node.js 服务器
package.json
运行下面的代码片段在服务器文件夹中创建一个文件。
cd server
npm init -y
安装 Express.js、CORS、Nodemon 和 Socket.io 服务器 API。
Express.js 是一个快速、简约的框架,它提供了多种用于在 Node.js 中构建 Web 应用程序的功能。CORS 是一个 Node.js包 ,允许不同域之间进行通信。
Nodemon 是一个 Node.js 工具,它在检测到文件更改后会自动重启服务器,而 Socket.io 允许我们在服务器上配置实时连接。
npm install express cors nodemon socket.io
创建一个index.js
文件——Web 服务器的入口点。
touch index.js
使用 Express.js 设置一个简单的 Node.js 服务器。下面的代码片段会在浏览器中访问时返回一个 JSON 对象http://localhost:4000/api
。
//👇🏻 index.js
const express = require("express");
const app = express();
const PORT = 4000;
app.get("/api", (req, res) => {
res.json({
message: "Hello world",
});
});
app.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
导入 HTTP 和 CORS 库以允许客户端和服务器域之间进行数据传输。
const express = require("express");
const app = express();
const PORT = 4000;
//👇🏻 New imports
const http = require("http").Server(app);
const cors = require("cors");
app.use(cors());
app.get("/api", (req, res) => {
res.json({
message: "Hello world",
});
});
http.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
接下来,将Socket.io添加到项目中,以创建实时连接。在app.get()
代码块之前,复制以下代码。
//👇🏻 New imports
const socketIO = require("socket.io")(http, {
cors: {
origin: "http://localhost:5173",
},
});
//👇🏻 Add this before the app.get() block
socketIO.on("connection", (socket) => {
console.log(`⚡: ${socket.id} user just connected!`);
socket.on("disconnect", () => {
console.log("🔥: A user disconnected");
});
});
从上面的代码片段中可以看出,该socket.io("connection")
函数与 React 应用程序建立连接,为每个套接字创建唯一的 ID,并在用户访问网页时将 ID 记录到控制台。
当您刷新或关闭网页时,套接字会触发断开事件以表明用户已与套接字断开连接。
接下来,通过将启动命令添加到文件中的脚本列表中来配置 Nodemon package.json
。下面的代码片段使用 Nodemon 启动服务器。
//👇🏻In server/package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.js"
},
您现在可以使用以下命令通过 Nodemon 运行服务器。
npm start
打开App.jsx
客户端文件夹中的文件并将 React 应用程序连接到 Socket.io 服务器。
import socketIO from "socket.io-client";
const socket = socketIO.connect("http://localhost:4000");
function App() {
return (
<div>
<p>Hello World!</p>
</div>
);
}
通过运行下面的代码片段启动 React.js 服务器。
npm run dev
检查服务器的终端;React.js 客户端的 ID 将会显示出来。恭喜🥂,您已成功通过 Socket.io 将 React 应用连接到服务器。
向您的应用添加身份验证👤
Clerk 是一款功能齐全的用户管理套件,可让您为软件应用程序添加各种形式的身份验证。使用 Clerk,您可以通过密码登录、无密码登录、社交帐户登录、短信验证以及 Web3 身份验证来验证用户身份。
Clerk 还提供预构建的身份验证组件,让您能够轻松地验证用户身份,从而将更多精力放在应用程序的逻辑上。这些组件也可自定义。
在本文中,我将带你了解
- 将 Clerk 添加到 React 应用,
- 使用 Clerk 验证用户身份,
- 使用Socket.io发送实时消息 ,以及
- 向 React 应用程序添加新颖的文本编辑器。
将 Clerk 添加到您的应用
在这里,你将学习如何通过 Clerk 验证用户身份。在继续之前,请先创建一个 Clerk 帐户。
创建一个新的Clerk应用程序,如下所示。
将您的可发布密钥复制到.env
React 应用程序内的文件中。
VITE_REACT_APP_CLERK_PUBLISHABLE_KEY=<your_publishable_key>
最后,更新App.jsx
文件以显示Clerk 提供的 UI 组件Signup
。Signin
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import socketIO from "socket.io-client";
import {
ClerkProvider,
SignedIn,
SignedOut,
SignIn,
SignUp,
RedirectToSignIn,
} from "@clerk/clerk-react";
//👇🏻 gets the publishable key
const clerkPubKey = import.meta.env.VITE_REACT_APP_CLERK_PUBLISHABLE_KEY;
//👇🏻 socketIO configuration
const socket = socketIO.connect("http://localhost:4000");
const App = () => {
return (
<Router>
<ClerkProvider publishableKey={clerkPubKey}>
<Routes>
<Route
path='/*'
element={
<div className='login'>
<SignIn
path='/'
routing='path'
signUpUrl='/register'
afterSignInUrl='/chat'
/>{" "}
</div>
}
/>
<Route
path='/register/*'
element={
<div className='login'>
<SignUp afterSignUpUrl='/chat' />
</div>
}
/>
<Route
path='/chat'
element={
<>
<SignedIn>
<Home socket={socket} />
</SignedIn>
<SignedOut>
<RedirectToSignIn />
</SignedOut>
</>
}
/>
</Routes>
</ClerkProvider>
</Router>
);
};
export default App;
- 从上面的代码片段来看,
创建一个包含该文件的 components 文件夹Home.jsx
。这将是聊天应用程序的主页。
cd client/src
mkdir components
touch Home.jsx
将下面的代码复制到Home.jsx
文件中以复制上面的聊天 UI。
import { useState } from "react";
import { Link } from "react-router-dom";
import { SignOutButton, useAuth } from "@clerk/clerk-react";
const Home = ({ socket }) => {
const { isLoaded, userId } = useAuth();
const [write, setWrite] = useState(false);
const writeFunction = () => setWrite(true);
const handleSubmit = () => {
console.log({ message: "Submit Clicked!", userId });
setWrite(false);
};
// In case the user signs out while on the page.
if (!isLoaded || !userId) {
return null;
}
return (
<div>
<nav className='navbar'>
<Link to='/' className='logo'>
Mingle
</Link>
<SignOutButton signOutCallback={() => console.log("Signed out!")}>
<button className='signOutBtn'>Sign out</button>
</SignOutButton>
</nav>
{!write ? (
<main className='chat'>
<div className='chat__body'>
<div className='chat__content'>
{/**-- contains chat messages-- */}
</div>
<div className='chat__input'>
<div className='chat__form'>
<button className='createBtn' onClick={writeFunction}>
Write message
</button>
</div>
</div>
</div>
<aside className='chat__bar'>
<h3>Active users</h3>
<ul>
<li>David</li>
<li>Dima</li>
</ul>
</aside>
</main>
) : (
<main className='editor'>
<header className='editor__header'>
<button className=' editorBtn' onClick={handleSubmit}>
SEND MESSAGE
</button>
</header>
<div className='editor__container'>Your editor container</div>
</main>
)}
</div>
);
};
export default Home;
从上面的代码片段可以看出,Clerk 提供了一个SignOutButton
组件和一个useAuth
钩子。该SignOutButton
组件可以包裹自定义按钮标签,而useAuth
钩子使我们能够访问当前用户的 ID。我们将使用用户的 ID 在 Node.js 服务器上识别用户。
当用户点击“写消息”按钮时,UI 会切换到编辑器屏幕。在接下来的部分中,您将学习如何将 Novel 文本编辑器添加到 React 应用。
将下一代编辑器添加到我们的聊天中💬
Novel 是一款 Notion 风格的所见即所得编辑器,支持多种文本格式和图片上传,并提供 AI 自动补全功能。
通过运行下面的代码片段来安装 Novel。
npm install novel
将编辑器组件导入到Home.jsx
组件中。
import { Editor } from "novel";
import "novel/styles.css";
将编辑器组件添加到 UI,如下所示。
将编辑器组件添加到 UI,如下所示。
<div>
<main className='editor'>
<header className='editor__header'>
<button className=' editorBtn' onClick={handleSubmit}>
SEND MESSAGE
</button>
</header>
<div className='editor__container'>
{/**-- 👇🏻 Editor component --**/}
<Editor onUpdate={(e) => setValue(updateMessage(e.content))} />
</div>
</main>
</div>
将下面的代码片段添加到组件以访问用户的输入。
//👇🏻 holds the Editor's content
const [value, setValue] = useState([]);
//👇🏻 saves only the heading and paragraph texts
const updateMessage = (array) => {
const elements = [];
for (let i = 0; i < array.length; i++) {
if (array[i].type === "paragraph" || array[i].type === "heading") {
elements.push(array[i].content[0].text);
}
}
return elements.join("\n");
};
处理实时通信👨👨👧
在这里,您将学习如何将用户的消息发送到 Node.js 服务器以及如何查看在线用户。
创建一个handleSubmit
函数,当用户点击按钮时,将编辑器的内容和用户的 ID 发送到服务器Send Message
。
const handleSubmit = () => {
socket.emit("message", {
value,
userId: userId.slice(0, 10),
});
setWrite(false);
};
更新 Node.js 服务器上的 Socket.io 监听器来监听该message
事件。
socketIO.on("connection", (socket) => {
console.log(`⚡: ${socket.id} user just connected!`);
//👇🏻 receives the data from the React app
socket.on("message", (data) => {
console.log(data);
socketIO.emit("messageResponse", data);
});
socket.on("disconnect", () => {
console.log("🔥: A user disconnected");
});
});
上面的代码片段从事件接收数据message
并将消息发送回 React 应用程序以显示其内容。
将事件监听器添加到 React 应用程序。
//👇🏻 holds online users
const [onlineUsers, setOnlineUsers] = useState([]);
//👇🏻 holds all the messages
const [messages, setMessages] = useState([]);
useEffect(() => {
socket.on("messageResponse", (data) => {
setMessages([...messages, data]);
if (!onlineUsers.includes(data.userId)) {
setOnlineUsers([...onlineUsers, data.userId]);
}
});
}, [socket, messages, onlineUsers]);
上面的代码片段将新消息添加到messages
数组中,onlineUsers
如果用户的 ID 不在列表中,则更新状态。
向下滚动查看新消息 🆕
在本节中,您将学习如何在有新消息时将滚动条移动到最新消息。
useRef
使用滚动消息容器底部的钩子创建一个新的 ref 。
import { useRef, useEffect } from "react";
const lastMessageRef = useRef(null);
useEffect(() => {
// 👇️ scroll to bottom every time messages change
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
添加一个带有 ref 属性的 div 元素,如下所示。
<div>
{/** --- messages container ---*/}
<div ref={lastMessageRef} />
</div>
当有新消息时,滚动条会聚焦在最新消息上。
恭喜!🎉您已完成此项目。
结论
到目前为止,您已经学习了如何使用 Clerk 对用户进行身份验证,在 React 和 Node.js 应用程序中通过 Socket.io 发送实时消息,以及向 React 应用程序添加 Novel WYSIWYG 编辑器。
Socket.io 是一款出色的工具,可用于构建需要实时通信的高效应用程序。Clerk 是 一款出色的身份验证管理系统,提供各种形式的身份验证。它也是开源的——您可以请求定制功能,或作为开发者为该工具做出贡献。
本教程的源代码可以在这里找到:
https://github.com/novuhq/blog/tree/main/chat-app-with-websockets-novel
感谢您的阅读!
文章来源:https://dev.to/novu/building-a-real-time-chat-with-websockets-novel-and-clerk-40ac