动态

详情 返回 返回

使用 Socket.io 和 React 開發一個聊天應用 - 动态 详情

這篇文章是關於什麼的

相信大家對於 web 版的線上聊天室都不陌生,這篇文章主要講的就是如何使用 Socket.io 和 React 開發一下簡單的線上聊天應用。

所謂線上聊天應用,就是你給一個人或者一個羣發送一條消息,他們可以看到這條消息並且可以回覆。既簡單又複雜。

開發一個聊天室應用,你需要在新信息來到的時候及時的感知到。

通常來説,為了獲得服務端的信息,我們需要發送一個 HTTP 請求。但是如果使用 websockets,服務端能夠主動告知你有新的信息到來而不需要客户端發起請求。

在本文中,我們將利用 Socket.io 提供的實時通信來創建一個開放式聊天應用程序,允許用户在該應用程序上發送和接收來自多個用户的消息。同時你也將學習到如何檢測用户是否在線以及用户什麼時候在輸入。

💡 為了更好的掌握這篇文章講到的內容,你可能需要先掌握關於 React.js 和 Node.js 的基礎知識

什麼是 Socket.io

Socket.io 是一個十分流行的 JavaScript 庫。它允許我們在瀏覽器和 Node.js 服務端之間創建一個實時的,雙向的通信。它是一個高性能並且可靠的庫,經過優化可以以最小的延遲處理大量數據。它遵循 WebSocket 協議並提供更好的功能,比如降級為 HTTP 長鏈接或者自動重連,這些功能可以協助我們構建一個高效的實時的應用。

如何通過 Socket.io 連接 React.js 應用到 Node.js 服務

在這個章節中,我們將開始為聊天室應用搭建項目環境。您還將學習如何將 Socket.io 添加到 React 和 Node.js 應用程序,並連接兩個開發服務器以通過 Socket.io 進行實時通信。

創建項目目錄,並創建兩個子目錄名為 client 和 server

$ mkdir chat-app
$ cd chat-app
$ mkdir client server

進入到 client 目錄下,通過終端創建 React.js 項目

$ cd client
$ npx create-react-app ./

安裝 Socket.io 客户端 API 以及 React Router

$ npm install socket.io-client react-router-dom

從 React 項目中刪除冗餘的文件像是 logo 和 測試文件,像下面一樣更新 App.js 文件來顯示 Hello World

function App() {
  return (
    <div>
      <p>Hello World!</p>
    </div>
  );
}

接下來,進入 server 目錄下,創建一個 package.json 文件

$ cd server
$ npm init -y

安裝 Express.js , CORS , Nodemon , 和 Socket.io 服務端 API

$ npm install express cors nodemon socket.io

創建一個 index.js 文件,作為 web 服務器的入口文件

touch index.js

使用 Express 創建一個簡單的 Node.js 服務。當我們在瀏覽器訪問 http://localhost:4000/api 的時候,下面的代碼會返回一個 JSON 對象

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

// 新導入的模塊
const server = 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 到項目中去創建一個實時連接

// 新導入
const { Server } = require("socket.io");

// 創建實時連接
const socketIO = new Server(server, {
    cors: {
        origin: "<http://localhost:3000>"
    }
});

// app.get 代碼省略 ...

// 監聽連接
socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} 用户已連接!`);
    socket.on('disconnect', () => {
        console.log('🔥: 一個用户已斷開連接');
    });
});

從上面的代碼中看到,每當用户訪問頁面的時候,socket.io("connection") 建立了與 React 應用的連接,然後為每個 socket 創建一個唯一的 ID ,然後將 ID 打印到控制枱

當你刷新或者關閉頁面,socket 會觸發 disconnect 事件,顯示一個用户已從 socket 斷開鏈接。

接下來,通過在 package.json 中添加 scripts 啓動來配置 Nodemon 。代碼如下

// 在 server/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
 },

你可以通過下面的命令來通過 Nodemon 啓動服務

$ npm start

打開 client 目錄下的 App.js 文件,把 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 服務

// client 目錄下運行

$ npm start

在客户端和服務端的服務都啓動之後,React 應用的 ID 會顯示在服務端的終端上。

恭喜 🥂,React 應用已經通過 Socket.io 成功連接到服務端

💡 對於本文的其餘部分,我將引導你完成為聊天應用程序創建網頁以及在 React 應用程序和 Node.js 服務器之間來回發送消息的過程。 我還將指導你如何在收到新消息時添加自動滾動功能以及如何在你的聊天應用程序中獲取活躍用户

為聊天應用創建主頁

在本章節中,我們將為聊天應用創建一個主頁。主頁會接收用户名並將其保存到本地存儲以供識別

client/src 目錄下創建一個名為 components 的目錄。然後,創建主頁的組件

$ cd src
$ mkdir components & cd components
$ touch Home.js

將下面的代碼複製到 Home.js 文件中。 該代碼片段顯示一個表單輸入,該表單輸入接受用户名並將其存儲在本地存儲中。

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const Home = () => {
  const navigate = useNavigate();
  const [userName, setUserName] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    localStorage.setItem('userName', userName);
    navigate('/chat');
  };
  return (
    <form className="home__container" onSubmit={handleSubmit}>
      <h2 className="home__header">登錄聊天</h2>
      <label htmlFor="username">用户名</label>
      <input
        type="text"
        minLength={6}
        name="username"
        id="username"
        className="username__input"
        value={userName}
        onChange={(e) => setUserName(e.target.value)}
      />
      <button className="home__cta">登錄</button>
    </form>
  );
};

export default Home;

接下來配置 React Router 來讓聊天應用的不同頁面可以相互跳轉。對於這個應用來説,主頁和聊天頁就足夠了

把下面的代碼複製到 src/App.js 文件中

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './components/Home';
import ChatPage from './components/ChatPage';
import socketIO from 'socket.io-client';

const socket = socketIO.connect('<http://localhost:4000>');
function App() {
  return (
    <BrowserRouter>
      <div>
        <Routes>
          <Route path="/" element={<Home socket={socket} />}></Route>
          <Route path="/chat" element={<ChatPage socket={socket} />}></Route>
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

該代碼片段使用 React Router v6 為應用程序的主頁和聊天頁面分配不同的路由並且將 socket.io 庫傳到組件中。我們將在接下來的章節中創建聊天頁面。

跳到 src/index.css 文件,複製下面的代碼。它包含了這個項目所需要用到的所有 CSS

@import url('<https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap>');

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: 'Poppins', sans-serif;
}
.home__container {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.home__container > * {
  margin-bottom: 10px;
}
.home__header {
  margin-bottom: 30px;
}
.username__input {
  padding: 10px;
  width: 50%;
}
.home__cta {
  width: 200px;
  padding: 10px;
  font-size: 16px;
  cursor: pointer;
  background-color: #607eaa;
  color: #f9f5eb;
  outline: none;
  border: none;
  border-radius: 5px;
}
.chat {
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
}
.chat__sidebar {
  height: 100%;
  background-color: #f9f5eb;
  flex: 0.2;
  padding: 20px;
  border-right: 1px solid #fdfdfd;
}
.chat__main {
  height: 100%;
  flex: 0.8;
}
.chat__header {
  margin: 30px 0 20px 0;
}
.chat__users > * {
  margin-bottom: 10px;
  color: #607eaa;
  font-size: 14px;
}
.online__users > * {
  margin-bottom: 10px;
  color: rgb(238, 102, 102);
  font-style: italic;
}
.chat__mainHeader {
  width: 100%;
  height: 10vh;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px;
  background-color: #f9f5eb;
}
.leaveChat__btn {
  padding: 10px;
  width: 150px;
  border: none;
  outline: none;
  background-color: #d1512d;
  cursor: pointer;
  color: #eae3d2;
}
.message__container {
  width: 100%;
  height: 80vh;
  background-color: #fff;
  padding: 20px;
  overflow-y: scroll;
}

.message__container > * {
  margin-bottom: 10px;
}
.chat__footer {
  padding: 10px;
  background-color: #f9f5eb;
  height: 10vh;
}
.form {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.message {
  width: 80%;
  height: 100%;
  border-radius: 10px;
  border: 1px solid #ddd;
  outline: none;
  padding: 15px;
}
.sendBtn {
  width: 150px;
  background-color: green;
  padding: 10px;
  border: none;
  outline: none;
  color: #eae3d2;
  cursor: pointer;
}
.sendBtn:hover {
  background-color: rgb(129, 201, 129);
}
.message__recipient {
  background-color: #f5ccc2;
  width: 300px;
  padding: 10px;
  border-radius: 10px;
  font-size: 15px;
}
.message__sender {
  background-color: rgb(194, 243, 194);
  max-width: 300px;
  padding: 10px;
  border-radius: 10px;
  margin-left: auto;
  font-size: 15px;
}
.message__chats > p {
  font-size: 13px;
}
.sender__name {
  text-align: right;
}
.message__status {
  position: fixed;
  bottom: 50px;
  font-size: 13px;
  font-style: italic;
}

我們已經創建了主頁,接下來我們開始設計聊天頁面的樣式和交互

為應用創建聊天頁

在這個章節,我們將創建聊天界面,允許我們發送消息和看到在線用户

1.png

從上面的圖片可以看到,聊天頁面被分成三個部分:ChatBar(側邊欄顯示活躍的用户);ChatBody (包含發送的消息和頭部);ChatFooter (包含發送輸入框和發送按鈕)

既然我們已經定義了聊天頁面的佈局,現在可以創建一個這樣佈局的組件

創建 ChatPage.js 文件,複製下面的代碼。你需要組建 ChatBar,ChatBody 和 ChatFooter

import React from 'react';
import ChatBar from './ChatBar';
import ChatBody from './ChatBody';
import ChatFooter from './ChatFooter';

const ChatPage = ({ socket }) => {
  return (
    <div className="chat">
      <ChatBar />
      <div className="chat__main">
        <ChatBody />
        <ChatFooter />
      </div>
    </div>
  );
};

export default ChatPage;

Char Bar 組件

複製下面的代碼到 ChatBar.js 文件中

import React from 'react';

const ChatBar = () => {
  return (
    <div className="chat__sidebar">
      <h2>自由聊天</h2>

      <div>
        <h4 className="chat__header">在線用户</h4>
        <div className="chat__users">
          <p>User 1</p>
          <p>User 2</p>
          <p>User 3</p>
          <p>User 4</p>
        </div>
      </div>
    </div>
  );
};

export default ChatBar;

Chat Body 組件

這裏我們創建一個組件來顯示已經發送的消息和標題

import React from 'react';
import { useNavigate } from 'react-router-dom';

const ChatBody = () => {
  const navigate = useNavigate();

  const handleLeaveChat = () => {
    localStorage.removeItem('userName');
    navigate('/');
    window.location.reload();
  };

  return (
    <>
      <header className="chat__mainHeader">
        <p>Hangout with Colleagues</p>
        <button className="leaveChat__btn" onClick={handleLeaveChat}>
          LEAVE CHAT
        </button>
      </header>

      {/* 顯示你發送消息的內容 */}
      <div className="message__container">
        <div className="message__chats">
          <p className="sender__name">You</p>
          <div className="message__sender">
            <p>Hello there</p>
          </div>
        </div>

        {/*顯示你接收消息的內容*/}
        <div className="message__chats">
          <p>Other</p>
          <div className="message__recipient">
            <p>Hey, I'm good, you?</p>
          </div>
        </div>

        {/* 當有用户正在輸入,則被觸發 */}
        <div className="message__status">
          <p>Someone is typing...</p>
        </div>
      </div>
    </>
  );
};

export default ChatBody;

Chat Footer 組件

在這裏,我們將在聊天頁面底部創建輸入和發送按鈕。 提交表單後,消息和用户名出現在控制枱中。

import React, { useState } from 'react';

const ChatFooter = () => {
  const [message, setMessage] = useState('');

  const handleSendMessage = (e) => {
    e.preventDefault();
    console.log({ userName: localStorage.getItem('userName'), message });
    setMessage('');
  };
  return (
    <div className="chat__footer">
      <form className="form" onSubmit={handleSendMessage}>
        <input
          type="text"
          placeholder="編寫消息"
          className="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
        />
        <button className="sendBtn">發送</button>
      </form>
    </div>
  );
};

export default ChatFooter;

在 React 應用和 Socket.io 服務之間發送消息

在本節中,您將學習如何通過 Socket.io 將消息從 React 應用程序發送到 Node.js 服務器,反之亦然。 要將消息發送到服務器,我們需要將 Socket.io 庫傳遞到 ChatFooter - 發送消息的組件

更新 ChatPage.js 文件以將 Socket.io 庫傳遞到 ChatFooter 組件中

import React from 'react';
import ChatBar from './ChatBar';
import ChatBody from './ChatBody';
import ChatFooter from './ChatFooter';

const ChatPage = ({ socket }) => {
  return (
    <div className="chat">
      <ChatBar />
      <div className="chat__main">
        <ChatBody />
        <ChatFooter socket={socket} />
      </div>
    </div>
  );
};

export default ChatPage;

更新 ChatFooter 組件中的 handleSendMessage 函數以將消息發送到 Node.js 服務器

import React, { useState } from 'react';

const ChatFooter = ({ socket }) => {
  const [message, setMessage] = useState('');

  const handleSendMessage = (e) => {
    e.preventDefault();
    if (message.trim() && localStorage.getItem('userName')) {
      socket.emit('message', {
        text: message,
        name: localStorage.getItem('userName'),
        id: `${socket.id}${Math.random()}`,
        socketID: socket.id,
      });
    }
    setMessage('');
  };
  return <div className="chat__footer">...</div>;
};

export default ChatFooter;

handleSendMessage 函數在發送包含用户輸入、用户名、生成的消息 ID 以及 socket 或客户端的消息事件之前檢查文本字段是否為空以及用户名是否存在於本地存儲(從主頁登錄) Node.js 服務器的 ID。

打開服務器的 index.js 文件,更新 Socket.io 相關代碼以監聽來自 React 應用程序客户端的消息事件,並將消息打印到服務器的終端

socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} 用户已連接!`);

  // 監聽和在控制枱打印消
  socket.on('message', (data) => {
    console.log(data);
  });

  socket.on('disconnect', () => {
    console.log('🔥: 一個用户已斷開連接');
  });
});

我們已經能夠在服務器上檢索消息; 因此,讓我們將消息發送給所有連接的客户端。


socketIO.on('connection', (socket) => {
  console.log(`⚡: ${socket.id} 用户已連接!`);

    // 發送信息給到所有在線的用户
  socket.on('message', (data) => {
    socketIO.emit('messageResponse', data);
  });

  socket.on('disconnect', () => {
    console.log('🔥: 一個用户已斷開連接');
  });
});

更新 ChatPage.js 文件以監聽來自服務器的消息並將其顯示給所有用户

import React, { useEffect, useState } from 'react';
import ChatBar from './ChatBar';
import ChatBody from './ChatBody';
import ChatFooter from './ChatFooter';

const ChatPage = ({ socket }) => {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    socket.on('messageResponse', (data) => setMessages([...messages, data]));
  }, [socket, messages]);

  return (
    <div className="chat">
      <ChatBar socket={socket} />
      <div className="chat__main">
        <ChatBody messages={messages} />
        <ChatFooter socket={socket} />
      </div>
    </div>
  );
};

export default ChatPage;

從上面的代碼片段中,Socket.io 監聽通過 messageResponse 事件發送的消息並將數據傳到消息數組中。 消息數組被傳遞到 ChatBody 組件以顯示在 UI 上

更新 ChatBody.js 文件來渲染消息數組中的數據

import React from 'react';
import { useNavigate } from 'react-router-dom';

const ChatBody = ({ messages }) => {
  const navigate = useNavigate();

  const handleLeaveChat = () => {
    localStorage.removeItem('userName');
    navigate('/');
    window.location.reload();
  };

  return (
    <>
      <header className="chat__mainHeader">
        <p>與朋友聚會</p>
        <button className="leaveChat__btn" onClick={handleLeaveChat}>
          離開聊天
        </button>
      </header>

      <div className="message__container">
        {messages.map((message) =>
          message.name === localStorage.getItem('userName') ? (
            <div className="message__chats" key={message.id}>
              <p className="sender__name">You</p>
              <div className="message__sender">
                <p>{message.text}</p>
              </div>
            </div>
          ) : (
            <div className="message__chats" key={message.id}>
              <p>{message.name}</p>
              <div className="message__recipient">
                <p>{message.text}</p>
              </div>
            </div>
          )
        )}

        <div className="message__status">
          <p>有用户正在輸入...</p>
        </div>
      </div>
    </>
  );
};

export default ChatBody;

上面的代碼片段根據是你或是其他用户發送了消息來顯示消息。 綠色的消息是你發送的消息,紅色的是來自其他用户的消息。

恭喜 🥂,聊天應用現在可以正常使用了。 你可以打開多個 Tab 並將消息從一個 Tab 發送到另一個 Tab

如何從 Socket.io 獲取活躍用户

在本節中,你將學習如何獲取所有活躍用户並將他們顯示在聊天應用程序的聊天欄上。

2.png

打開 src/Home.js 並創建一個在用户登錄時監聽的事件。更新 handleSubmit 函數如下:

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const Home = ({ socket }) => {
  const navigate = useNavigate();
  const [userName, setUserName] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    localStorage.setItem('userName', userName);
        // 發送用户名和 socket ID 到 Node.js 服務器
    socket.emit('newUser', { userName, socketID: socket.id });
    navigate('/chat');
  };
  return (...)
  ...

創建一個事件監聽器,每當用户加入或離開聊天應用時,它就會更新 Node.js 服務器上的 users 數組。

let users = [];

socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} 用户已連接!`);
  socket.on('message', (data) => {
    socketIO.emit('messageResponse', data);
  });

  // 監聽新用户的加入
  socket.on('newUser', (data) => {
    // 添加新用户到 users 中
    users.push(data);
    // console.log(users);
    // 發送用户列表到客户端
    socketIO.emit('newUserResponse', users);
  });

  socket.on('disconnect', () => {
        console.log('🔥: 一個用户已斷開連接');
    // 當用户下線的時候更新用户列表
    users = users.filter((user) => user.socketID !== socket.id);
    // console.log(users);
    // 發送用户列表到客户端
    socketIO.emit('newUserResponse', users);
    socket.disconnect();
  });
});

當有新用户加入聊天應用,socket.on("newUser") 就會被觸發。用户的詳細信息(Socket ID 和用户名)保存到 users 數組中,並在名為 newUserResponse 的新事件中發送回 React 應用程序

socket.io("disconnect") 中,當用户離開聊天應用程序時更新 user 數組,並觸發 newUserReponse 事件以將更新的用户列表發送到客户端。

接下來,讓我們更新用户界面 ChatBar.js ,來顯示活躍用户列表

import React, { useState, useEffect } from 'react';

const ChatBar = ({ socket }) => {
    const [users, setUsers] = useState([]);
    useEffect(() => {
        socket.on('newUserResponse', (data) => setUsers(data));
    }, [socket, users]);
    
    return (
        <div className="chat__sidebar">
        <h2>自由聊天</h2>

        <div>
            <h4 className="chat__header">在線用户</h4>
            <div className="chat__users">
                {users.map((user) => (
                    <p key={user.socketID}>{user.userName}</p>
                ))}
            </div>
        </div>
        </div>
    );
};

export default ChatBar;

useEffect hook 監聽從 Node.js 服務器發送的響應並收集活躍用户列表。 該列表被映射到視圖中並實時更新

恭喜 💃🏻,我們已經能夠從 Socket.io 中獲取活躍用户列表。 接下來,讓我們學習如何向聊天應用添加一些很酷的功能

可選:自動滾動和通知有用户正在輸入

在本節中,您將瞭解如何在收到新消息時添加自動滾動功能以及標識用户正在鍵入的功能

3.png

像下方代碼一樣更新 ChatPage.js

import React, { useEffect, useState, useRef } from 'react';
import ChatBar from './ChatBar';
import ChatBody from './ChatBody';
import ChatFooter from './ChatFooter';

const ChatPage = ({ socket }) => {
    const [messages, setMessages] = useState([]);
    const [typingStatus, setTypingStatus] = useState('');
    const lastMessageRef = useRef(null);

    useEffect(() => {
        socket.on('messageResponse', (data) => setMessages([...messages, data]));
    }, [socket, messages]);

    useEffect(() => {
        // 👇️ 每當消息文字變動,都會往下滾動
        lastMessageRef.current?.scrollIntoView({ behavior: 'smooth' });
    }, [messages]);

    useEffect(() => {
        socket.on('typingResponse', (data) => setTypingStatus(data));
      }, [socket]);

    return (
        <div className="chat">
        <ChatBar socket={socket}  />
        <div className="chat__main">
            <ChatBody 
                messages={messages} 
                typingStatus={typingStatus}
                lastMessageRef={lastMessageRef}  />
            <ChatFooter socket={socket} />
        </div>
        </div>
    );
};

export default ChatPage;

更新 ChatBody 組件來包含一個帶有 lastMessageRef 的元素

import React from 'react';
import { useNavigate } from 'react-router-dom';

const ChatBody = ({ messages, lastMessageRef }) => {
  const navigate = useNavigate();

  const handleLeaveChat = () => {
    localStorage.removeItem('userName');
    navigate('/');
    window.location.reload();
  };

  return (
    <>
      <div>
        ......
        {/* --- 在 JSX 代碼的最底部 ----*/}
        <div ref={lastMessageRef} />
      </div>
    </>
  );
};

export default ChatBody;

從上面的代碼片段來看,lastMessageRef 附加到消息底部的一個 div 標籤,它的 useEffect 有一個依賴項,即 messages 數組。 因此,當消息更改時,lastMessageRef 的 useEffect 會重新渲染。

通知其他人有用户正在輸入

為了在用户輸入時通知其他用户,我們將在輸入框上使用 JavaScript onKeyDown 事件監聽器,它會觸發一個向 Socket.io 發送消息的函數,如下所示:

import React, { useState } from 'react';

const ChatFooter = ({socket}) => {
  const [message, setMessage] = useState('');

  const handleTyping = () =>
    socket.emit('typing', `${localStorage.getItem('userName')} 正在輸入`);

  const handleSendMessage = (e) => {
    e.preventDefault();
    if (message.trim() && localStorage.getItem('userName')) {
        socket.emit('message', {
          text: message,
          name: localStorage.getItem('userName'),
          id: `${socket.id}${Math.random()}`,
          socketID: socket.id,
        });
      }
    setMessage('');
  };
  return (
    <div className="chat__footer">
      <form className="form" onSubmit={handleSendMessage}>
        <input
          type="text"
          placeholder="編寫消息"
          className="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          onKeyDown={handleTyping}

        />
        <button className="sendBtn">發送</button>
      </form>
    </div>
  );
};

export default ChatFooter;

在上面的代碼片段中,handleTyping 函數會在用户使用 input 輸入框鍵入時觸發鍵入事件。 然後,我們可以在服務器上監聽 typing 事件,並通過另一個名為 typingResponse 的事件向其他用户發送包含數據的響應

socketIO.on('connection', (socket) => {
 //   console.log(`⚡: ${socket.id} 用户已連接!`);

    // 監聽和在控制枱打印消息
   // socket.on('message', (data) => {
   //     console.log(data);
   //     socketIO.emit('messageResponse', data);
   //  });

    socket.on('typing', (data) => socket.broadcast.emit('typingResponse', data));

    // 監聽新用户的加入
    // socket.on('newUser', (data) => {
        // 添加新用户到 users 中
    //    users.push(data);
        // console.log(users);

        // 發送用户列表到客户端
    //    socketIO.emit('newUserResponse', users);
    // });

    // ...
});

接下來監聽 ChatPage.js 文件中的 typingResponse 事件,將數據傳入 ChatBody.js 文件進行顯示

import React, { useEffect, useState, useRef } from 'react';
import ChatBar from './ChatBar';
import ChatBody from './ChatBody';
import ChatFooter from './ChatFooter';

const ChatPage = ({ socket }) => {
    // const [messages, setMessages] = useState([]);
    // const [typingStatus, setTypingStatus] = useState('');
    // const lastMessageRef = useRef(null);

    // useEffect(() => {
    //     socket.on('messageResponse', (data) => setMessages([...messages, data]));
    // }, [socket, messages]);

    useEffect(() => {
        // 👇️ 每當消息文字變動,都會往下滾動
        lastMessageRef.current?.scrollIntoView({ behavior: 'smooth' });
    }, [messages]);

    useEffect(() => {
        socket.on('typingResponse', (data) => setTypingStatus(data));
      }, [socket]);

    return (
        <div className="chat">
        <ChatBar socket={socket}  />
        <div className="chat__main">
            <ChatBody 
                messages={messages} 
                typingStatus={typingStatus}
                lastMessageRef={lastMessageRef}  />
            <ChatFooter socket={socket} />
        </div>
        </div>
    );
};

export default ChatPage;

更新 ChatBody.js 文件去給用户顯示輸入狀態

<div className="message__status">
  <p>{typingStatus}</p>
</div>

恭喜,您創建了一個聊天應用程序!💃🏻

通過添加允許用户創建 私人聊天室 和 直接消息傳遞 的 Socket.io 私人消息傳遞功能,使用用於用户授權和身份驗證的身份驗證庫以及用於存儲的實時數據庫,隨意改進應用程序。

總結

Socket.io 是一個非常棒的工具,具有出色的功能,使我們能夠構建高效的實時應用程序,例如體育網站、拍賣和外匯交易應用程序,當然還有通過在 Web 瀏覽器和 Node.js 服務器之間創建持久連接的聊天應用程序

如果你期待在 Node.js 中構建聊天應用程序,Socket.io 可能是一個很好的選擇

你可以在此處找到本教程的源代碼:https://github.com/zidanDirk/...

參考文章:

  • https://dev.to/novu/building-...
  • socket.io
user avatar yinzhixiaxue 头像 littlelyon 头像 u_17443142 头像 jiavan 头像 hankin_liu 头像 nznznz 头像 yangxiansheng_5a1b9b93a3a44 头像 congjunhua 头像 user_ze46ouik 头像 fanudekaixinguo 头像 kasong 头像 mi2nagemao 头像
点赞 48 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.