Développement d'un chat React à l'aide de Socket.IO





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

      
      







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.



All Articles