Bonne journée, mes amis!
Je voudrais partager avec vous mon expérience de développement d'un chat simple dans React en utilisant la bibliothèque Socket.IO .
Il est supposé que vous connaissez la bibliothèque nommée. Si vous n'êtes pas familier, voici un guide connexe avec des exemples de création d'une tudushka et de chat en JavaScript vanille .
Il suppose également que vous êtes au moins superficiellement familier avec Node.js .
Dans cet article, je vais me concentrer sur les aspects pratiques de l'utilisation de Socket.IO, React et Node.js.
Notre chat aura les principales caractéristiques suivantes:
- Sélection de chambre
- Envoi de messages
- Supprimer les messages de l'expéditeur
- Stockage des messages dans une base de données locale au format JSON
- Stockage du nom d'utilisateur et de l'ID dans le stockage local du navigateur
- Affichage du nombre d'utilisateurs actifs
- Affichage d'une liste d'utilisateurs avec un indicateur en ligne
Nous mettrons également en œuvre la possibilité d'envoyer des emoji .
Si vous êtes intéressé, suivez-moi.
Pour ceux qui ne sont intéressés que par le code: voici le lien vers le référentiel .
Bac Ă sable:
Structure du projet et dépendances
Commençons par créer un projet:
mkdir react-chat
cd react-chat
Créez un client à l'aide de Create React App :
yarn create react-app client
#
npm init react-app client
#
npx create-react-app client
À l'avenir, j'utiliserai yarn : pour installer les dépendances
yarn add = npm i, yarn start = npm start, yarn dev = npm run dev
.
Allez dans le répertoire "client" et installez des dépendances supplémentaires:
cd client
yarn add socket.io-client react-router-dom styled-components bootstrap react-bootstrap react-icons emoji-mart react-timeago
- socket.io-client - côté client Socket.IO
- react-router-dom - routage
- styled-components - styling (CSS-in-JS)
- bootstrap , react-bootstrap - style
- react-icons - icĂ´nes
- emoji-mart - emoji
- react-timeago - formatage de la date et de l'heure
La section "dépendances" du fichier "package.json":
{
"bootstrap": "^4.6.0",
"emoji-mart": "^3.0.0",
"react": "^17.0.1",
"react-bootstrap": "^1.5.0",
"react-dom": "^17.0.1",
"react-icons": "^4.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"react-timeago": "^5.2.0",
"socket.io-client": "^3.1.0",
"styled-components": "^5.2.1"
}
Revenez au répertoire racine (react-chat), créez le répertoire "serveur", allez-y, initialisez le projet et installez les dépendances:
cd ..
mkdir server
cd server
yarn init -yp
yarn add socket.io lowdb supervisor
- socket.io - backend Socket.IO
- lowdb - base de données locale au format JSON
- superviseur - serveur de développement (alternative à nodemon , qui ne fonctionne pas correctement avec la dernière version stable de Node.js; cela a quelque chose à voir avec un démarrage / arrêt incorrect des processus enfants)
Ajoutez la commande "start" pour démarrer le serveur de production et la commande "dev" pour démarrer le serveur de développement. package.json:
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"lowdb": "^1.0.0",
"socket.io": "^3.1.0",
"supervisor": "^0.12.0"
},
"scripts": {
"start": "node index.js",
"dev": "supervisor index.js"
}
}
Revenez au répertoire racine (react-chat), initialisez le projet et installez les dépendances:
cd .. yarn init -yp yarn add nanoid concurrently
- nanoïdes - générant des identifiants (seront utilisés à la fois sur le client et sur le serveur)
- simultanément - exécution simultanée de deux commandes ou plus
react-chat / package.json (notez que les commandes pour npm sont différentes; voir la documentation simultanée):
{
"name": "react-chat",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"concurrently": "^6.0.0",
"nanoid": "^3.1.20"
},
"scripts": {
"server": "yarn --cwd server dev",
"client": "yarn --cwd client start",
"start": "concurrently \"yarn server\" \"yarn client\""
}
}
Super, nous en avons terminé avec la formation de la structure principale du projet et l'installation des dépendances nécessaires. Commençons par implémenter le serveur.
Implémentation serveur
La structure du répertoire "serveur":
|--server |--db - |--handlers |--messageHandlers.js |--userHandlers.js |--index.js ...
Dans le fichier "index.js", nous procédons comme suit:
- Construire un serveur HTTP
- Nous y connectons Socket.IO
- Nous démarrons le serveur sur le port 5000
- Enregistrement des gestionnaires d'événements lors de la connexion d'un socket
index.js:
// HTTP-
const server = require('http').createServer()
// Socket.IO
const io = require('socket.io')(server, {
cors: {
origin: '*'
}
})
const log = console.log
//
const registerMessageHandlers = require('./handlers/messageHandlers')
const registerUserHandlers = require('./handlers/userHandlers')
// (, = )
const onConnection = (socket) => {
//
log('User connected')
// ""
const { roomId } = socket.handshake.query
//
socket.roomId = roomId
// ( )
socket.join(roomId)
//
//
registerMessageHandlers(io, socket)
registerUserHandlers(io, socket)
// -
socket.on('disconnect', () => {
//
log('User disconnected')
//
socket.leave(roomId)
})
}
//
io.on('connection', onConnection)
//
const PORT = process.env.PORT || 5000
server.listen(PORT, () => {
console.log(`Server ready. Port: ${PORT}`)
})
Dans le fichier "handlers / messageHandlers.js", nous procédons comme suit:
- Configurer une base de données locale au format JSON en utilisant lowdb
- Nous écrivons les données initiales dans la base de données
- Création de fonctions pour recevoir, ajouter et supprimer des messages
- Nous enregistrons le traitement des événements correspondants:
- message: obtenir - recevoir des messages
- message: ajouter - ajouter un message
- message: supprimer - supprimer un message
Les messages sont des objets avec les propriétés suivantes:
- messageId (chaîne) - identificateur de message
- userId (string) - ID utilisateur
- senderName (chaîne) - nom de l'expéditeur
- messageText (string) - texte du message
- createdAt (date) - date de création
handlers / messageHandlers.js:
const { nanoid } = require('nanoid')
//
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
// "db" "messages.json"
const adapter = new FileSync('db/messages.json')
const db = low(adapter)
//
db.defaults({
messages: [
{
messageId: '1',
userId: '1',
senderName: 'Bob',
messageText: 'What are you doing here?',
createdAt: '2021-01-14'
},
{
messageId: '2',
userId: '2',
senderName: 'Alice',
messageText: 'Go back to work!',
createdAt: '2021-02-15'
}
]
}).write()
module.exports = (io, socket) => {
//
const getMessages = () => {
//
const messages = db.get('messages').value()
// ,
// - , ,
io.in(socket.roomId).emit('messages', messages)
}
//
//
const addMessage = (message) => {
db.get('messages')
.push({
// nanoid, 8 - id
messageId: nanoid(8),
createdAt: new Date(),
...message
})
.write()
//
getMessages()
}
//
// id
const removeMessage = (messageId) => {
db.get('messages').remove({ messageId }).write()
getMessages()
}
//
socket.on('message:get', getMessages)
socket.on('message:add', addMessage)
socket.on('message:remove', removeMessage)
}
Dans le fichier "handlers / userHandlers.js", nous procédons comme suit:
- Créer une structure normalisée avec les utilisateurs
- Nous créons des fonctions pour obtenir, ajouter et supprimer des utilisateurs
- Nous enregistrons le traitement des événements correspondants:
- user: get - get users
- user: add - ajouter un utilisateur
- user: quitter - supprimer un utilisateur
Nous pourrions également utiliser lowdb pour travailler avec la liste des utilisateurs. Vous pouvez le faire si vous le souhaitez. Avec votre permission, je me limiterai à l'objet.
La structure normalisée (objet) des utilisateurs a le format suivant:
{ id (string) - : { username (string) - , online (boolean) - } }
En fait, nous ne supprimons pas les utilisateurs, mais transférons leur statut en mode hors ligne (attribuant la propriété «en ligne» à «faux»).
handlers / userHandlers.js:
//
//
const users = {
1: { username: 'Alice', online: false },
2: { username: 'Bob', online: false }
}
module.exports = (io, socket) => {
//
// "roomId" ,
// ,
//
const getUsers = () => {
io.in(socket.roomId).emit('users', users)
}
//
// id
const addUser = ({ username, userId }) => {
// ,
if (!users[userId]) {
// ,
users[userId] = { username, online: true }
} else {
// ,
users[userId].online = true
}
//
getUsers()
}
//
const removeUser = (userId) => {
// ,
// (O(1))
// ()
// redux, , immer,
users[userId].online = false
getUsers()
}
//
socket.on('user:get', getUsers)
socket.on('user:add', addUser)
socket.on('user:leave', removeUser)
}
Nous démarrons le serveur pour vérifier ses performances:
yarn dev
Si nous voyons le message «Serveur prêt. Port: 5000 ", et un fichier" messages.json "avec les données initiales est apparu dans le répertoire" db ", ce qui signifie que le serveur fonctionne comme prévu, et vous pouvez procéder à l'implémentation de la partie client.
Implémentation client
Avec le client, tout est un peu plus compliqué. La structure du répertoire "client":
|--client |--public |--index.html |--src |--components |--ChatRoom |--MessageForm |--MessageForm.js |--package.json |--MessageList |--MessageList.js |--MessageListItem.js |--package.json |--UserList |--UserList.js |--package.json |--ChatRoom.js |--package.json |--Home |--Home.js |--package.json |--index.js |--hooks |--useBeforeUnload.js |--useChat.js |--useLocalStorage.js App.js index.js |--jsconfig.json ( src) ...
Comme son nom l'indique, le répertoire "components" contient des composants d'application (parties de l'interface utilisateur, modules) et le répertoire "hooks" contient des hooks utilisateur ("custom"), dont le principal est useChat ().
Les fichiers "package.json" dans les répertoires des composants ont un seul champ "main" avec la valeur du chemin vers le fichier JS, par exemple:
{
"main": "./Home"
}
Cela vous permet d'importer un composant à partir d'un répertoire sans spécifier de nom de fichier, par exemple:
import { Home } from './Home'
//
import { Home } from './Home/Home'
Les fichiers "components / index.js" et "hooks / index.js" sont respectivement utilisés pour agréger et réexporter des composants et des hooks.
composants / index.js:
export { Home } from './Home'
export { ChatRoom } from './ChatRoom'
hooks / index.js:
export { useChat } from './useChat'
export { useLocalStorage } from './useLocalStorage'
export { useBeforeUnload } from './useBeforeUnload'
Cela vous permet à nouveau d'importer des composants et des hooks par répertoire et en même temps. L'agrégation et la réexportation entraînent l'utilisation d'exportations de composants nommés (la documentation React recommande d'utiliser l'exportation par défaut).
Le fichier jsconfig.json ressemble Ă ceci:
{
"compilerOptions": {
"baseUrl": "src"
}
}
Ceci "indique" au compilateur que l'importation des modules commence à partir du répertoire "src", ainsi les composants, par exemple, peuvent être importés comme ceci:
//
import { Home, ChatRoom } from 'components'
//
import { Home, ChatRoom } from './components'
Commençons par regarder les crochets personnalisés.
Vous pouvez utiliser des solutions toutes faites. Par exemple, voici les hooks proposés par la bibliothèque react-use :
#
yarn add react-use
#
import { useLocalStorage } from 'react-use'
import { useBeforeUnload } from 'react-use'
Le hook useLocalStorage () vous permet de stocker (écrire et récupérer) des valeurs dans le stockage local du navigateur. Nous l'utiliserons pour stocker le nom d'utilisateur et l'ID utilisateur entre les sessions du navigateur. Nous ne voulons pas forcer l'utilisateur à saisir son nom à chaque fois, mais l'ID est nécessaire pour déterminer les messages appartenant à cet utilisateur. Le hook prend un nom pour la clé et, éventuellement, une valeur initiale.
hooks / useLocalstorage.js:
import { useState, useEffect } from 'react'
export const useLocalStorage = (key, initialValue) => {
const [value, setValue] = useState(() => {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
})
useEffect(() => {
const item = JSON.stringify(value)
window.localStorage.setItem(key, item)
// , key, useEffect, ,
// useEffect
// eslint-disable-next-line
}, [value])
return [value, setValue]
}
Le hook "useBeforeUnload ()" est utilisé pour afficher un message ou exécuter une fonction lorsque la page (onglet du navigateur) est rechargée ou fermée. Nous l'utiliserons pour envoyer un événement "user: Leave" au serveur pour basculer le statut de l'utilisateur. Une tentative d'implémentation de la distribution de l'événement spécifié à l'aide du rappel renvoyé par le hook "useEffect ()" a échoué. Le hook prend un paramètre, une primitive ou une fonction.
hooks / useBeforeUnload.js:
import { useEffect } from 'react'
export const useBeforeUnload = (value) => {
const handleBeforeunload = (e) => {
let returnValue
if (typeof value === 'function') {
returnValue = value(e)
} else {
returnValue = value
}
if (returnValue) {
e.preventDefault()
e.returnValue = returnValue
}
return returnValue
}
useEffect(() => {
window.addEventListener('beforeunload', handleBeforeunload)
return () => window.removeEventListener('beforeunload', handleBeforeunload)
// eslint-disable-next-line
}, [])
}
Le hook useChat () est le hook principal de notre application. Ce sera plus facile si je le commente ligne par ligne.
hooks / useChat.js:
import { useEffect, useRef, useState } from 'react'
// IO
import io from 'socket.io-client'
import { nanoid } from 'nanoid'
//
import { useLocalStorage, useBeforeUnload } from 'hooks'
//
// -
const SERVER_URL = 'http://localhost:5000'
//
export const useChat = (roomId) => {
//
const [users, setUsers] = useState([])
//
const [messages, setMessages] = useState([])
//
const [userId] = useLocalStorage('userId', nanoid(8))
//
const [username] = useLocalStorage('username')
// useRef() DOM-,
//
const socketRef = useRef(null)
useEffect(() => {
// ,
// ""
// socket.handshake.query.roomId
socketRef.current = io(SERVER_URL, {
query: { roomId }
})
// ,
// id
socketRef.current.emit('user:add', { username, userId })
//
socketRef.current.on('users', (users) => {
//
setUsers(users)
})
//
socketRef.current.emit('message:get')
//
socketRef.current.on('messages', (messages) => {
// , ,
// "userId" id ,
// "currentUser" "true",
// ,
const newMessages = messages.map((msg) =>
msg.userId === userId ? { ...msg, currentUser: true } : msg
)
//
setMessages(newMessages)
})
return () => {
//
socketRef.current.disconnect()
}
}, [roomId, userId, username])
//
//
const sendMessage = ({ messageText, senderName }) => {
// id
socketRef.current.emit('message:add', {
userId,
messageText,
senderName
})
}
// id
const removeMessage = (id) => {
socketRef.current.emit('message:remove', id)
}
// "user:leave"
useBeforeUnload(() => {
socketRef.current.emit('user:leave', userId)
})
// ,
return { users, messages, sendMessage, removeMessage }
}
Par défaut, toutes les demandes des clients sont envoyées à localhost: 3000 (le port sur lequel le serveur de développement s'exécute). Pour rediriger les requêtes vers le port sur lequel s'exécute le serveur "serveur", un proxy doit être effectué. Pour ce faire, ajoutez la ligne suivante au fichier "src / package.json":
"proxy": "http://localhost:5000"
Il reste à implémenter les composants de l'application.
Le composant Accueil est la première chose que voit l'utilisateur lorsqu'il lance l'application. Il contient un formulaire dans lequel l'utilisateur est invité à saisir son nom et à sélectionner une chambre. En réalité, dans le cas d'une salle, l'utilisateur n'a pas le choix, une seule option (gratuite) est disponible. La deuxième option (désactivée) (tâche) est la possibilité de mettre à l'échelle l'application. L'affichage du bouton pour démarrer une discussion dépend du champ avec le nom de l'utilisateur (lorsque ce champ est vide, le bouton n'est pas affiché). Le bouton est en fait un lien vers la page de discussion.
composants / Home.js:
import { useState, useRef } from 'react'
// react-router-dom
import { Link } from 'react-router-dom'
//
import { useLocalStorage } from 'hooks'
// react-bootstrap
import { Form, Button } from 'react-bootstrap'
export function Home() {
//
//
const [username, setUsername] = useLocalStorage('username', 'John')
//
const [roomId, setRoomId] = useState('free')
const linkRef = useRef(null)
//
const handleChangeName = (e) => {
setUsername(e.target.value)
}
//
const handleChangeRoom = (e) => {
setRoomId(e.target.value)
}
//
const handleSubmit = (e) => {
e.preventDefault()
//
linkRef.current.click()
}
const trimmed = username.trim()
return (
<Form
className='mt-5'
style={{ maxWidth: '320px', margin: '0 auto' }}
onSubmit={handleSubmit}
>
<Form.Group>
<Form.Label>Name:</Form.Label>
<Form.Control value={username} onChange={handleChangeName} />
</Form.Group>
<Form.Group>
<Form.Label>Room:</Form.Label>
<Form.Control as='select' value={roomId} onChange={handleChangeRoom}>
<option value='free'>Free</option>
<option value='job' disabled>
Job
</option>
</Form.Control>
</Form.Group>
{trimmed && (
<Button variant='success' as={Link} to={`/${roomId}`} ref={linkRef}>
Chat
</Button>
)}
</Form>
)
}
Le composant UserList, comme son nom l'indique, est une liste d'utilisateurs. Il contient un accordéon, la liste elle-même et des indicateurs de présence en ligne des utilisateurs.
composants / UserList.js:
//
import { Accordion, Card, Button, Badge } from 'react-bootstrap'
// -
import { RiRadioButtonLine } from 'react-icons/ri'
// -
export const UserList = ({ users }) => {
//
const usersArr = Object.entries(users)
// ( )
// [ ['1', { username: 'Alice', online: false }], ['2', {username: 'Bob', online: false}] ]
//
const activeUsers = Object.values(users)
//
// [ {username: 'Alice', online: false}, {username: 'Bob', online: false} ]
.filter((u) => u.online).length
return (
<Accordion className='mt-4'>
<Card>
<Card.Header bg='none'>
<Accordion.Toggle
as={Button}
variant='info'
eventKey='0'
style={{ textDecoration: 'none' }}
>
Active users{' '}
<Badge variant='light' className='ml-1'>
{activeUsers}
</Badge>
</Accordion.Toggle>
</Card.Header>
{usersArr.map(([userId, obj]) => (
<Accordion.Collapse eventKey='0' key={userId}>
<Card.Body>
<RiRadioButtonLine
className={`mb-1 ${
obj.online ? 'text-success' : 'text-secondary'
}`}
size='0.8em'
/>{' '}
{obj.username}
</Card.Body>
</Accordion.Collapse>
))}
</Card>
</Accordion>
)
}
Le composant MessageForm est un formulaire standard pour l'envoi de messages. Picker est un composant emoji fourni par la bibliothèque emoji-mart. Ce composant est affiché / masqué en appuyant sur un bouton.
composants / MessageForm.js:
import { useState } from 'react'
//
import { Form, Button } from 'react-bootstrap'
//
import { Picker } from 'emoji-mart'
//
import { FiSend } from 'react-icons/fi'
import { GrEmoji } from 'react-icons/gr'
//
export const MessageForm = ({ username, sendMessage }) => {
//
const [text, setText] = useState('')
//
const [showEmoji, setShowEmoji] = useState(false)
//
const handleChangeText = (e) => {
setText(e.target.value)
}
// /
const handleEmojiShow = () => {
setShowEmoji((v) => !v)
}
//
// ,
const handleEmojiSelect = (e) => {
setText((text) => (text += e.native))
}
//
const handleSendMessage = (e) => {
e.preventDefault()
const trimmed = text.trim()
if (trimmed) {
sendMessage({ messageText: text, senderName: username })
setText('')
}
}
return (
<>
<Form onSubmit={handleSendMessage}>
<Form.Group className='d-flex'>
<Button variant='primary' type='button' onClick={handleEmojiShow}>
<GrEmoji />
</Button>
<Form.Control
value={text}
onChange={handleChangeText}
type='text'
placeholder='Message...'
/>
<Button variant='success' type='submit'>
<FiSend />
</Button>
</Form.Group>
</Form>
{/* */}
{showEmoji && <Picker onSelect={handleEmojiSelect} emojiSize={20} />}
</>
)
}
Le composant MessageListItem est un élément de liste de messages. TimeAgo est un composant de formatage de la date et de l'heure. Il prend une date et renvoie une chaîne comme "il y a 1 mois". Cette ligne est mise à jour en temps réel. Seul l'utilisateur qui les a envoyés peut supprimer les messages.
composants / MessageListItem.js:
//
import TimeAgo from 'react-timeago'
//
import { ListGroup, Card, Button } from 'react-bootstrap'
//
import { AiOutlineDelete } from 'react-icons/ai'
//
export const MessageListItem = ({ msg, removeMessage }) => {
//
const handleRemoveMessage = (id) => {
removeMessage(id)
}
const { messageId, messageText, senderName, createdAt, currentUser } = msg
return (
<ListGroup.Item
className={`d-flex ${currentUser ? 'justify-content-end' : ''}`}
>
<Card
bg={`${currentUser ? 'primary' : 'secondary'}`}
text='light'
style={{ width: '55%' }}
>
<Card.Header className='d-flex justify-content-between align-items-center'>
{/* TimeAgo */}
<Card.Text as={TimeAgo} date={createdAt} className='small' />
<Card.Text>{senderName}</Card.Text>
</Card.Header>
<Card.Body className='d-flex justify-content-between align-items-center'>
<Card.Text>{messageText}</Card.Text>
{/* */}
{currentUser && (
<Button
variant='none'
className='text-warning'
onClick={() => handleRemoveMessage(messageId)}
>
<AiOutlineDelete />
</Button>
)}
</Card.Body>
</Card>
</ListGroup.Item>
)
}
Le composant "MessageList" est une liste de messages. Il utilise le composant "MessageListItem".
composants / MessageList.js:
import { useRef, useEffect } from 'react'
//
import { ListGroup } from 'react-bootstrap'
//
import { MessageListItem } from './MessageListItem'
// (inline styles)
const listStyles = {
height: '80vh',
border: '1px solid rgba(0,0,0,.4)',
borderRadius: '4px',
overflow: 'auto'
}
//
// "MessageListItem"
export const MessageList = ({ messages, removeMessage }) => {
// ""
const messagesEndRef = useRef(null)
// ,
useEffect(() => {
messagesEndRef.current?.scrollIntoView({
behavior: 'smooth'
})
}, [messages])
return (
<>
<ListGroup variant='flush' style={listStyles}>
{messages.map((msg) => (
<MessageListItem
key={msg.messageId}
msg={msg}
removeMessage={removeMessage}
/>
))}
<span ref={messagesEndRef}></span>
</ListGroup>
</>
)
}
Le composant App est le composant principal de l'application. Il définit les itinéraires et assemble l'interface.
src / App.js:
//
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
//
import { Container } from 'react-bootstrap'
//
import { Home, ChatRoom } from 'components'
//
const routes = [
{ path: '/', name: 'Home', Component: Home },
{ path: '/:roomId', name: 'ChatRoom', Component: ChatRoom }
]
export const App = () => (
<Router>
<Container style={{ maxWidth: '512px' }}>
<h1 className='mt-2 text-center'>React Chat App</h1>
<Switch>
{routes.map(({ path, Component }) => (
<Route key={path} path={path} exact>
<Component />
</Route>
))}
</Switch>
</Container>
</Router>
)
Enfin, le fichier "src / index.js" est le point d'entrée JavaScript pour Webpack. Il fait le style et le rendu globaux du composant App.
src / index.js:
import React from 'react'
import { render } from 'react-dom'
import { createGlobalStyle } from 'styled-components'
//
import 'bootstrap/dist/css/bootstrap.min.css'
import 'emoji-mart/css/emoji-mart.css'
//
import { App } from './App'
// ""
const GlobalStyles = createGlobalStyle`
.card-header {
padding: 0.25em 0.5em;
}
.card-body {
padding: 0.25em 0.5em;
}
.card-text {
margin: 0;
}
`
const root = document.getElementById('root')
render(
<>
<GlobalStyles />
<App />
</>,
root
)
Eh bien, nous avons fini de développer notre petite application.
Il est temps de s'assurer que cela fonctionne. Pour ce faire, dans le répertoire racine du projet (react-chat), exécutez la commande "yarn start". Après cela, dans l'onglet du navigateur qui s'ouvre, vous devriez voir quelque chose comme ceci:
Au lieu d'une conclusion
Si vous souhaitez améliorer l'application, voici quelques idées:
- Ajouter DB pour les utilisateurs (en utilisant le mĂŞme lowdb)
- Ajouter une deuxième salle - pour cela, il suffit de mettre en œuvre un traitement séparé des listes de messages sur le serveur
- ( ) —
- — MongoDB Cloud Mongoose; Express
- : (, , ..) — react-filepond, — multer; WebRTC
- Du plus exotique: ajoutez une voix off au texte et traduisez les messages vocaux en texte - vous pouvez utiliser react-speech-kit pour cela
Certaines de ces idées sont incluses dans mes plans pour améliorer le chat.
Merci pour votre attention et bonne journée.