О чем эта статья?

Мы все сталкивались с общением в Интернете, это может быть Facebook, Instagram, WhatsApp, и этот список можно продолжить.

Для пояснения: вы отправляете сообщение человеку или группе, они видят это сообщение и отвечают вам. Простой, но сложный.

Чтобы разработать приложение для чата, вам нужно быть в курсе новых сообщений, как только они приходят.

Обычно для получения информации с сервера нужно отправить HTTP-запрос. С помощью веб-сокетов сервер сообщает вам, когда появляется новая информация, не спрашивая ее.

В этой статье мы будем использовать связь в реальном времени, предоставляемую Socket.io, для создания приложения открытого чата, которое позволяет пользователям отправлять и получать сообщения от нескольких пользователей в приложении. Вы также узнаете, как определить пользователей, которые находятся в сети, и когда пользователь печатает.

Чтобы прочитать эту статью, вам необходимо иметь базовые знания о React.js и Node.js, чтобы понять эту статью.

Что такое Socket.io?

Socket.io — это популярная библиотека JavaScript, которая позволяет нам создавать двустороннюю связь в режиме реального времени между веб-браузерами и сервером Node.js. Это высокопроизводительная и надежная библиотека, оптимизированная для обработки больших объемов данных с минимальной задержкой.

Он следует протоколу WebSocket и предоставляет улучшенные функциональные возможности, такие как возврат к длительному опросу HTTP или автоматическое повторное подключение, что позволяет нам создавать эффективные приложения для чата и реального времени.

Novu — первая архитектура уведомлений с открытым исходным кодом

Просто краткая справка о нас. Нову — первая инфраструктура уведомлений с открытым исходным кодом. Мы в основном помогаем управлять всеми уведомлениями о продуктах. Это может быть In-App (значок колокольчика, как в Facebook — Websockets), электронная почта, SMS и так далее.

Сейчас мы в тренде на Github, и каждая дополнительная звезда может помочь нам привлечь больше разработчиков, буду рад, если вы поможете мне ❤️
https://github.com/novuhq/novu

Как подключить приложение React.js к Node.js через Socket.io

В этом разделе мы настроим среду проекта для нашего чат-приложения. Вы также узнаете, как добавить Socket.io в приложение React и Node.js и соединить оба сервера разработки для связи в реальном времени через Socket.io.

Создайте папку проекта, содержащую две подпапки с именами client и server.

mkdir chat-app
cd chat-app
mkdir client server

Перейдите в папку клиента через свой терминал и создайте новый проект React.js.

cd client
npx create-react-app ./

Установите клиентский API Socket.io и React Router. React Router — это библиотека JavaScript, которая позволяет нам перемещаться между страницами в приложении React.

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

Удалите лишние файлы, такие как логотип и тестовые файлы, из приложения React и обновите файл App.js, чтобы отобразить Hello World, как показано ниже.

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

Затем перейдите в папку сервера и создайте файл package.json.

cd server
npm init -y

Установите Express.js, CORS, Nodemon и Socket.io Server API.

Express.js — это быстрый минималистичный фреймворк, предоставляющий несколько функций для создания веб-приложений на Node.js. CORS — это пакет Node.js, который обеспечивает связь между разными доменами.

Nodemon — это инструмент Node.js, который автоматически перезапускает сервер после обнаружения изменений в файле, а Socket.io позволяет нам настроить соединение в реальном времени на сервере.

npm install express cors nodemon socket.io

Создайте файл index.js — точку входа на веб-сервер.

touch index.js

Настройте простой сервер Node.js с помощью Express.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:3000"
    }
});
//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, затем создает уникальный идентификатор для каждого сокета и записывает идентификатор в консоль всякий раз, когда пользователь посещает веб-страницу.

Когда вы обновляете или закрываете веб-страницу, сокет запускает событие разъединения, показывающее, что пользователь отключился от сокета.

Затем настройте 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.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.

npm start

Проверьте терминал, на котором запущен сервер; ID клиента React.js появится в терминале.

Поздравляем 🥂, приложение React успешно подключено к серверу через Socket.io.

💡 В оставшейся части этой статьи я проведу вас через создание веб-страниц для приложения чата и отправку сообщений туда и обратно между приложением React и сервером Node.js. Я также расскажу вам, как добавить функцию автоматической прокрутки при поступлении нового сообщения и как получить активных пользователей в приложении чата.

Создание домашней страницы для приложения чата

В этом разделе мы создадим домашнюю страницу для приложения чата, которое принимает имя пользователя и сохраняет его в локальном хранилище для идентификации.

Создайте папку с именем компоненты в папке client/src. Затем создайте компонент домашней страницы.

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">Sign in to Open Chat</h2>
      <label htmlFor="username">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">SIGN IN</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;
}

Мы создали домашнюю страницу нашего чат-приложения. Далее давайте разработаем пользовательский интерфейс для страницы чата.

Создание страницы чата приложения

В этом разделе мы создадим интерфейс чата, который позволит нам отправлять сообщения и просматривать активных пользователей.

На изображении выше страница чата разделена на три части: панель чата — боковая панель, показывающая активных пользователей, тело чата, содержащее отправленные сообщения и заголовок, и нижний колонтитул чата — окно сообщения и кнопка отправки.

Поскольку мы смогли определить макет страницы чата, теперь вы можете создать компоненты для дизайна.

Создайте файл 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;

Компонент «Панель чата»

Скопируйте приведенный ниже код в файл ChatBar.js.

import React from 'react';
const ChatBar = () => {
  return (
    <div className="chat__sidebar">
      <h2>Open Chat</h2>
      <div>
        <h4 className="chat__header">ACTIVE USERS</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;

Компонент «Тело чата»

Здесь мы создадим интерфейс, отображающий отправленные сообщения и заголовок страницы.

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>
      {/*This shows messages sent from you*/}
      <div className="message__container">
        <div className="message__chats">
          <p className="sender__name">You</p>
          <div className="message__sender">
            <p>Hello there</p>
          </div>
        </div>
        {/*This shows messages received by you*/}
        <div className="message__chats">
          <p>Other</p>
          <div className="message__recipient">
            <p>Hey, I'm good, you?</p>
          </div>
        </div>
        {/*This is triggered when a user is typing*/}
        <div className="message__status">
          <p>Someone is typing...</p>
        </div>
      </div>
    </>
  );
};
export default ChatBody;

Компонент нижнего колонтитула чата

Здесь мы создадим ввод и кнопку отправки внизу страницы чата. Сообщение и имя пользователя появляются в консоли после отправки формы.

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="Write message"
          className="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
        />
        <button className="sendBtn">SEND</button>
      </form>
    </div>
  );
};
export default ChatFooter;

Отправка сообщений между приложением React и сервером Socket.io

В этом разделе вы узнаете, как отправлять сообщения из приложения React на сервер Node.js и наоборот через Socket.io. Чтобы отправить сообщения на сервер, нам нужно будет передать библиотеку 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;

Обновите функцию handleSendMessage в компоненте ChatFooter, чтобы отправить сообщение на сервер 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 для сервера Node.js.

Откройте файл index.js на сервере, обновите блок кода Socket.io, чтобы прослушивать событие сообщения от клиента приложения React, и зарегистрируйте сообщение на терминале сервера.

socketIO.on('connection', (socket) => {
  console.log(`⚡: ${socket.id} user just connected!`);
  //Listens and logs the message to the console
  socket.on('message', (data) => {
    console.log(data);
  });
  socket.on('disconnect', () => {
    console.log('🔥: A user disconnected');
  });
});

Нам удалось получить сообщение на сервере; следовательно, давайте отправим сообщение всем подключенным клиентам.

socketIO.on('connection', (socket) => {
  console.log(`⚡: ${socket.id} user just connected!`);
  //sends the message to all the users on the server
  socket.on('message', (data) => {
    socketIO.emit('messageResponse', data);
  });
  socket.on('disconnect', () => {
    console.log('🔥: A user disconnected');
  });
});

Обновите файл 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 для отображения в пользовательском интерфейсе.

Обновите файл 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>Hangout with Colleagues</p>
        <button className="leaveChat__btn" onClick={handleLeaveChat}>
          LEAVE CHAT
        </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>Someone is typing...</p>
        </div>
      </div>
    </>
  );
};
export default ChatBody;

Приведенный выше фрагмент кода отображает сообщения в зависимости от того, кто отправил сообщение — вы или другой пользователь. Сообщения, выделенные зеленым цветом, — это те, которые вы отправили, а красным — сообщения от других пользователей.

Поздравляем 🥂, приложение чата теперь работает. Вы можете открывать несколько вкладок и отправлять сообщения из одной в другую.

Как получить активных пользователей из Socket.io

В этом разделе вы узнаете, как получить всех активных пользователей и отобразить их на панели чата приложения чата.

Откройте 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);
    //sends the username and socket ID to the Node.js server
    socket.emit('newUser', { userName, socketID: socket.id });
    navigate('/chat');
  };
  return (...)
  ...

Создайте прослушиватель событий, который обновляет массив пользователей на сервере Node.js всякий раз, когда пользователь присоединяется к приложению чата или покидает его.

let users = [];
socketIO.on('connection', (socket) => {
  console.log(`⚡: ${socket.id} user just connected!`);
  socket.on('message', (data) => {
    socketIO.emit('messageResponse', data);
  });
  //Listens when a new user joins the server
  socket.on('newUser', (data) => {
    //Adds the new user to the list of users
    users.push(data);
    // console.log(users);
    //Sends the list of users to the client
    socketIO.emit('newUserResponse', users);
  });
  socket.on('disconnect', () => {
    console.log('🔥: A user disconnected');
    //Updates the list of users when a user disconnects from the server
    users = users.filter((user) => user.socketID !== socket.id);
    // console.log(users);
    //Sends the list of users to the client
    socketIO.emit('newUserResponse', users);
    socket.disconnect();
  });
});

socket.on("newUser") запускается, когда новый пользователь присоединяется к приложению чата. Сведения о пользователе (идентификатор сокета и имя пользователя) сохраняются в массиве users и отправляются обратно в приложение React в новом событии с именем newUserResponse.
В socket.io("disconnect") массив users обновляется, когда пользователь покидает приложение чата, и событие 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>Open Chat</h2>
      <div>
        <h4 className="chat__header">ACTIVE USERS</h4>
        <div className="chat__users">
          {users.map((user) => (
            <p key={user.socketID}>{user.userName}</p>
          ))}
        </div>
      </div>
    </div>
  );
};
export default ChatBar;

Хук useEffect прослушивает ответ, отправленный с сервера Node.js, и собирает список активных пользователей. Список отображается в представлении и обновляется в режиме реального времени.

Поздравляем 💃🏻, нам удалось получить список активных пользователей из Socket.io. Далее давайте узнаем, как добавить некоторые интересные функции в приложение чата.

Необязательно: автоматическая прокрутка и уведомление пользователей, когда пользователь вводит текст.

В этом разделе вы узнаете, как добавить функцию автоматической прокрутки при получении нового сообщения и функцию ввода, указывающую, что пользователь печатает.

Функция автоматической прокрутки

Обновите файл 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(() => {
    // 👇️ scroll to bottom every time messages change
    lastMessageRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);
  return (
    <div className="chat">
      <ChatBar socket={socket} />
      <div className="chat__main">
        <ChatBody messages={messages} 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>
        ......
        {/* --- At the bottom of the JSX element ----*/}
        <div ref={lastMessageRef} />
      </div>
    </>
  );
};
export default ChatBody;

Из приведенных выше фрагментов кода lastMessageRef прикрепляется к тегу div в нижней части сообщений, а его useEffect имеет единственную зависимость — массив сообщений. Итак, когда сообщения изменяются, useEffect для lastMessageRef перерисовывается.

Уведомлять других, когда пользователь печатает

Чтобы уведомлять пользователей, когда пользователь печатает, мы будем использовать прослушиватель событий JavaScript onKeyDown в поле ввода, который запускает функцию, которая отправляет сообщение в Socket.io, как показано ниже:

import React, { useState } from 'react';
const ChatFooter = ({ socket }) => {
  const [message, setMessage] = useState('');
  const handleTyping = () =>
    socket.emit('typing', `${localStorage.getItem('userName')} is typing`);
  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="Write message"
          className="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
                    {/*OnKeyDown function*/}
          onKeyDown={handleTyping}
        />
        <button className="sendBtn">SEND</button>
      </form>
    </div>
  );
};
export default ChatFooter;

Из приведенного выше фрагмента кода функция handleTyping вызывает событие typing всякий раз, когда пользователь вводит текст в текстовое поле. Затем мы можем прослушать событие ввода на сервере и отправить ответ, содержащий данные, другим пользователям через другое событие с именем typingResponse.

socketIO.on('connection', (socket) => {
  // console.log(`⚡: ${socket.id} user just connected!`);
  // socket.on('message', (data) => {
  //   socketIO.emit('messageResponse', data);
  // });
  socket.on('typing', (data) => socket.broadcast.emit('typingResponse', data));
  // socket.on('newUser', (data) => {
  //   users.push(data);
  //   socketIO.emit('newUserResponse', users);
  // });
  // socket.on('disconnect', () => {
  //   console.log('🔥: A user disconnected');
  //   users = users.filter((user) => user.socketID !== socket.id);
  //   socketIO.emit('newUserResponse', users);
  //   socket.disconnect();
  // });
});

Затем прослушайте событие typingResponse в файле ChatPage.js и передайте данные в файл 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(() => {
  //   // 👇️ scroll to bottom every time messages change
  //   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 — отличный инструмент с отличными функциями, который позволяет нам создавать эффективные приложения в реальном времени, такие как веб-сайты для ставок на спорт, приложения для аукционов и торговли на рынке Форекс, и, конечно же, приложения для чата, создавая прочные соединения между веб-браузерами и сервером Node.js. .

Если вы хотите создать чат-приложение на Node.js, Socket.io может стать отличным выбором.

Вы можете найти исходный код этого руководства здесь: https://github.com/novuhq/blog/tree/main/open-chat-app-with-socketIO

Следующая статья

В следующей части цикла я расскажу о подключении чат-приложения к браузерным уведомлениям (web-push), чтобы вы могли информировать пользователей о новых сообщениях, если они не в сети.

Выручи меня!

Если вы считаете, что эта статья помогла вам лучше понять WebSockets! Я был бы очень рад, если бы вы могли дать нам звезду! И дайте мне знать в комментариях ❤️
https://github.com/novuhq/novu

Спасибо за чтение!

Станьте компонуемым: создавайте приложения быстрее, как Lego

Bit – это инструмент с открытым исходным кодом для модульного и совместного создания приложений. Перейдите на компоновку, чтобы поставлять быстрее, более последовательно и легко масштабировать.

Подробнее

Создавайте приложения, страницы, пользовательский опыт и пользовательские интерфейсы как автономные компоненты. Используйте их, чтобы быстрее создавать новые приложения и возможности. Используйте любой фреймворк и инструмент в своем рабочем процессе. Делитесь, повторно используйте и сотрудничайте, чтобы строить вместе.

Помогите своей команде:

Микроинтерфейсы

Дизайн-системы

Совместное использование кода и повторное использование

Монорепо

Узнать больше