Desarrollando un React Chat usando Socket.IO





¡Buen dia amigos!



Me gustaría compartir con ustedes mi experiencia de desarrollar un chat simple en React usando la biblioteca Socket.IO .



Se supone que está familiarizado con la biblioteca nombrada. Si no está familiarizado, aquí hay una guía relacionada con ejemplos de creación de un tudushka y chat en JavaScript vanilla .



También se supone que está al menos superficialmente familiarizados con Node.js .



En este artículo, me centraré en los aspectos prácticos del uso de Socket.IO, React y Node.js.



Nuestro chat tendrá las siguientes características principales:



  • Selección de habitación
  • Enviando mensajes
  • Eliminar mensajes del remitente
  • Almacenar mensajes en una base de datos local en formato JSON
  • Almacenar el nombre de usuario y la identificación en el almacenamiento local del navegador
  • Visualización del número de usuarios activos
  • Visualización de una lista de usuarios con un indicador en línea


También implementaremos la capacidad de enviar emoji .



Si estás interesado, sígueme.



Para aquellos que solo estén interesados ​​en el código: aquí está el enlace al repositorio .



Salvadera:





Estructura y dependencias del proyecto



Comencemos a crear un proyecto:



mkdir react-chat
cd react-chat

      
      





Cree un cliente usando la aplicación Create React :



yarn create react-app client
# 
npm init react-app client
# 
npx create-react-app client

      
      





En el futuro, usaré yarn : para instalar dependencias yarn add = npm i, yarn start = npm start, yarn dev = npm run dev



.



Vaya al directorio "cliente" e instale dependencias adicionales:



cd client
yarn add socket.io-client react-router-dom styled-components bootstrap react-bootstrap react-icons emoji-mart react-timeago

      
      







La sección "dependencias" del archivo "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"
}

      
      





Vuelva al directorio raíz (react-chat), cree el directorio "servidor", vaya a él, inicialice el proyecto e instale las dependencias:



cd ..
mkdir server
cd server
yarn init -yp
yarn add socket.io lowdb supervisor

      
      





  • socket.io - Backend de Socket.IO
  • lowdb - base de datos local en formato JSON
  • supervisor - servidor de desarrollo (alternativa a nodemon , que no funciona correctamente con la última versión estable de Node.js; tiene algo que ver con el inicio / detención incorrecto de procesos secundarios)


Agregue el comando "iniciar" para iniciar el servidor de producción y el comando "dev" para iniciar el servidor de desarrollo. 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"
  }
}

      
      





Regrese al directorio raíz (react-chat), inicialice el proyecto e instale las dependencias:



  cd ..
  yarn init -yp
  yarn add nanoid concurrently

      
      





  • identificadores generadores de nanoides (se utilizarán tanto en el cliente como en el servidor)
  • concurrentemente - ejecución simultánea de dos o más comandos


react-chat / package.json (tenga en cuenta que los comandos para npm se ven diferentes; consulte los documentos al mismo tiempo):



{
  "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\""
  }
}

      
      





Genial, hemos terminado con la formación de la estructura principal del proyecto y la instalación de las dependencias necesarias. Comencemos a implementar el servidor.



Implementación del servidor



La estructura del directorio "servidor":



|--server
  |--db -    
  |--handlers
    |--messageHandlers.js
    |--userHandlers.js
  |--index.js
  ...

      
      





En el archivo "index.js", hacemos lo siguiente:



  • Construyendo un servidor HTTP
  • Le conectamos Socket.IO
  • Iniciamos el servidor en el puerto 5000
  • Registro de controladores de eventos al conectar un enchufe


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}`)
})

      
      





En el archivo "handlers / messageHandlers.js" hacemos lo siguiente:



  • Configurar una base de datos local en formato JSON usando lowdb
  • Escribimos los datos iniciales en la base de datos.
  • Creación de funciones para recibir, agregar y eliminar mensajes
  • Registramos el procesamiento de los eventos correspondientes:

    • mensaje: obtener - recibir mensajes
    • mensaje: agregar - agregar un mensaje
    • mensaje: eliminar - eliminar un mensaje




Los mensajes son objetos con las siguientes propiedades:



  • messageId (cadena) - identificador del mensaje
  • userId (cadena) - ID de usuario
  • senderName (cadena) - nombre del remitente
  • messageText (cadena) - texto del mensaje
  • createdAt (fecha) - fecha de creación


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)
}

      
      





En el archivo "handlers / userHandlers.js" hacemos lo siguiente:



  • Crea una estructura normalizada con usuarios
  • Creamos funciones para obtener, agregar y eliminar usuarios
  • Registramos el procesamiento de los eventos correspondientes:

    • usuario: obtener - obtener usuarios
    • usuario: agregar - agregar un usuario
    • usuario: dejar - eliminar un usuario




También podríamos usar lowdb para trabajar con la lista de usuarios. Puedes hacer esto si quieres. Yo, con su permiso, me limitaré al objeto.



La estructura normalizada (objeto) de los usuarios tiene el siguiente formato:



{
  id (string) - : {
    username (string) -  ,
    online (boolean) -     
  }
}

      
      





De hecho, no estamos eliminando usuarios, sino transfiriendo su estado a fuera de línea (asignando la propiedad "en línea" a "falso").



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)
}

      
      





Arrancamos el servidor para comprobar su rendimiento:



yarn dev

      
      





Si vemos el mensaje “Servidor listo. Puerto: 5000 ", y un archivo" messages.json "con los datos iniciales apareció en el directorio" db ", lo que significa que el servidor está funcionando como se esperaba, y puede proceder a la implementación de la parte del cliente.



Implementación del cliente



Con el cliente todo es algo más complicado. La estructura del directorio "cliente":



|--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)
  ...

      
      





Como su nombre lo indica, el directorio "componentes" contiene componentes de la aplicación (partes de la interfaz de usuario, módulos) y el directorio "ganchos" contiene ganchos de usuario ("personalizados"), el principal de los cuales es useChat ().



Los archivos "package.json" en los directorios de componentes tienen un solo campo "principal" con el valor de la ruta al archivo JS, por ejemplo:



{
  "main": "./Home"
}

      
      





Esto le permite importar un componente desde un directorio sin especificar un nombre de archivo, por ejemplo:



import { Home } from './Home'
// 
import { Home } from './Home/Home'

      
      





Los archivos "components / index.js" y "hooks / index.js" se utilizan para agregar y reexportar componentes y hooks, respectivamente.



componentes / index.js:



export { Home } from './Home'
export { ChatRoom } from './ChatRoom'

      
      





ganchos / index.js:



export { useChat } from './useChat'
export { useLocalStorage } from './useLocalStorage'
export { useBeforeUnload } from './useBeforeUnload'

      
      





Esto nuevamente le permite importar componentes y enlaces por directorio y al mismo tiempo. La agregación y la reexportación provocan el uso de exportaciones de componentes con nombre (la documentación de React recomienda usar la exportación predeterminada).



El archivo jsconfig.json se ve así:



{
  "compilerOptions": {
    "baseUrl": "src"
  }
}

      
      





Esto "le dice" al compilador que la importación de módulos comienza desde el directorio "src", por lo que los componentes, por ejemplo, se pueden importar así:



//      
import { Home, ChatRoom } from 'components'
// 
import { Home, ChatRoom } from './components'

      
      





Comencemos mirando los ganchos personalizados.



Puede utilizar soluciones listas para usar. Por ejemplo, estos son los ganchos que ofrece la biblioteca react-use :



# 
yarn add react-use
# 
import { useLocalStorage } from 'react-use'
import { useBeforeUnload } from 'react-use'

      
      





El gancho useLocalStorage () le permite almacenar (escribir y recuperar) valores en el almacenamiento local del navegador. Lo usaremos para almacenar el nombre de usuario y la identificación de usuario entre sesiones de navegador. No queremos obligar al usuario a ingresar su nombre cada vez, pero la identificación es necesaria para determinar los mensajes que pertenecen a este usuario. El gancho acepta un nombre de clave y, opcionalmente, un valor inicial.



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]
}

      
      





El gancho "useBeforeUnload ()" se usa para mostrar un mensaje o ejecutar una función cuando la página (pestaña del navegador) se vuelve a cargar o se cierra. Lo usaremos para enviar un evento "usuario: dejar" al servidor para cambiar el estado del usuario. Un intento de implementar el envío del evento especificado usando la devolución de llamada devuelta por el gancho "useEffect ()" no tuvo éxito. El gancho toma un parámetro, una primitiva o una función.



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
  }, [])
}

      
      





El gancho useChat () es el gancho principal de nuestra aplicación. Será más fácil si lo comento línea por línea.



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 }
}

      
      





De forma predeterminada, todas las solicitudes de los clientes se envían a localhost: 3000 (el puerto en el que se ejecuta el servidor de desarrollo). Para redirigir las solicitudes al puerto en el que se ejecuta el servidor "servidor", se debe realizar un proxy. Para hacer esto, agregue la siguiente línea al archivo "src / package.json":



"proxy": "http://localhost:5000"

      
      





Queda por implementar los componentes de la aplicación.



El componente Inicio es lo primero que ve el usuario cuando inicia la aplicación. Tiene un formulario en el que se le pide al usuario que ingrese su nombre y seleccione una habitación. En realidad, en el caso de una habitación, el usuario no tiene otra opción, solo una opción (gratis) está disponible. La segunda opción (deshabilitada) (trabajo) es la capacidad de escalar la aplicación. La visualización del botón para iniciar un chat depende del campo con el nombre del usuario (cuando este campo está vacío, el botón no se muestra). El botón es en realidad un enlace a la página de chat.



componentes / 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>
  )
}

      
      





El componente UserList, como su nombre indica, es una lista de usuarios. Contiene un acordeón, la propia lista e indicadores de la presencia en línea de los usuarios.



componentes / 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>
  )
}

      
      





El componente MessageForm es un formulario estándar para enviar mensajes. Picker es un componente de emoji proporcionado por la biblioteca emoji-mart. Este componente se muestra / oculta presionando un botón.



componentes / 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} />}
    </>
  )
}

      
      





El componente MessageListItem es un elemento de lista de mensajes. TimeAgo es un componente para formatear la fecha y la hora. Toma una fecha y devuelve una cadena como "hace 1 mes". Esta línea se actualiza en tiempo real. Solo el usuario que los envió puede eliminar mensajes.



componentes / 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>
  )
}

      
      





El componente MessageList es una lista de mensajes. Utiliza el componente "MessageListItem".



componentes / 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>
    </>
  )
}

      
      





El componente de la aplicación es el componente principal de la aplicación. Define rutas y ensambla la interfaz.



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>
)

      
      





Finalmente, el archivo "src / index.js" es el punto de entrada de JavaScript para Webpack. Realiza el diseño y la representación global del componente de la aplicación.



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
)

      
      





Bueno, hemos terminado de desarrollar nuestra pequeña aplicación.



Es hora de asegurarse de que funcione. Para ello, en el directorio raíz del proyecto (react-chat) ejecutamos el comando "yarn start". Después de eso, en la pestaña del navegador que se abre, debería ver algo como esto:















En lugar de una conclusión



Si desea mejorar la aplicación, aquí hay un par de ideas:



  • Agregue DB para usuarios (usando el mismo lowdb)
  • Agregue una segunda sala: para esto, es suficiente implementar un procesamiento separado de listas de mensajes en el servidor
  • ( ) —
  • MongoDB Cloud Mongoose; Express
  • : (, , ..) — react-filepond, — multer; WebRTC
  • De los más exóticos: agregue voz en off al texto y traduzca los mensajes de voz en texto; puede usar react-speech-kit para esto


Algunas de estas ideas están incluidas en mis planes para mejorar el chat.



Gracias por su atención y que tenga un buen día.



All Articles