使用 Socket.io 和 React 開發一個聊天應用

superZidan發表於2022-12-28

這篇文章是關於什麼的

相信大家對於 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/...

參考文章:

相關文章