728x90
반응형

 

2021.06.04 - [FRONT-END/React] - [React] 리액트 & socket.io 기반 채팅 어플리케이션 만들기 - 2

 

[React] 리액트 & socket.io 기반 채팅 어플리케이션 만들기 - 2

1탄을 보지 않고 2탄부터 오셨다면 꼭 보고 오시길 바랍니다. 2021.06.04 - [FRONT-END/React] - [React] React로 Chat Application 구현하기 -1 [React] React로 Chat Application 구현하기 -1 https://www.youtu..

hello-ming.tistory.com

 

이어서 할것이다!

 

이번 포스팅에는 전 포스팅에서 폴더와 js파일을 만들어놨었는데 이번에는 js 내용을 채울것이다!

 

server에 users.js를 생성한 후 내용을 채운다.

 

const users = []

const addUser = ({ id, name, room }) => {
//이름의 공백 제거
  name = name.trim().toLowerCase()
  room = room.trim().toLowerCase()

  const existingUser = users.find(
    (user) => user.room === room && user.name === name
  )

  if (!name || !room) return { error: '사용자 이름과 방이 필요합니다.' }
  if (existingUser) return { error: '이미 사용중인 이름입니다.' }

  const user = { id, name, room }

  users.push(user)

  return { user }
}

const removeUser = (id) => {
  const index = users.findIndex((user) => user.id === id)

  if (index !== -1) return users.splice(index, 1)[0]
}

const getUser = (id) => users.find((user) => user.id === id)

const getUsersInRoom = (room) => users.filter((user) => user.room === room)

module.exports = { addUser, removeUser, getUser, getUsersInRoom }

 

기존에 만들어 놓은 server/index.js에 아래의 내용으로 바꿔준다.

const express = require('express')
const socketio = require('socket.io')
const http = require('http')

const cors = require('cors')
const router = require('./router')
const { addUser, removeUser, getUser, getUsersInRoom } = require('./users.js')

const PORT = process.env.PORT || 5000

const app = express()
const server = http.createServer(app)
const io = socketio(server)
app.use(cors())
app.use(router)

io.on('connection', (socket) => {
  console.log('새로운 connection이 발생하였습니다.')
  socket.on('join', ({ name, room }, callback) => {
    const { error, user } = addUser({ id: socket.id, name, room })
    if (error) callback({ error: '에러가 발생했어요.' })

    socket.emit('message', {
      user: 'admin',
      text: `${user.name}, ${user.room}에 오신것을 환영합니다.`,
    })
    socket.broadcast.to(user.room).emit('message', {
      user: 'admin',
      text: `${user.name} 님이 가입하셨습니다.`,
    })
    io.to(user.room).emit('roomData', {
      room: user.room,
      users: getUsersInRoom(user.room),
    })
    socket.join(user.room)

    callback()
  })
  socket.on('sendMessage', (message, callback) => {
    const user = getUser(socket.id)
    io.to(user.room).emit('message', { user: user.name, text: message })
    callback()
  })
  socket.on('disconnect', () => {
    const user = removeUser(socket.id)

    if (user) {
      io.to(user.room).emit('message', {
        user: 'Admin',
        text: `${user.name} 님이 방을 나갔습니다.`,
      })
      io.to(user.room).emit('roomData', {
        room: user.room,
        users: getUsersInRoom(user.room),
      })
    }
    console.log('유저가 떠났어요.')
  })
})
server.listen(PORT, () => console.log(`서버가 ${PORT} 에서 시작되었어요`))

 

Chat.js을 수정한다.

import React, { useState, useEffect } from 'react'
import queryString from 'query-string'
import io from 'socket.io-client'

import TextContainer from '../TextContainer/TextContainer'
import Messages from '../Messages/Messages'
import InfoBar from '../InfoBar/InfoBar'
import Input from '../Input/Input'

import './Chat.css'

const ENDPOINT = 'http://localhost:5000'

let socket

const Chat = ({ location }) => {
  const [name, setName] = useState('')
  const [room, setRoom] = useState('')
  const [users, setUsers] = useState('')
  const [message, setMessage] = useState('')
  const [messages, setMessages] = useState([])

  useEffect(() => {
    const { name, room } = queryString.parse(location.search)

    socket = io(ENDPOINT)

    setRoom(room)
    setName(name)

    socket.emit('join', { name, room }, (error) => {
      if (error) {
        alert(error)
      }
    })
  }, [ENDPOINT, location.search])

  useEffect(() => {
    socket.on('message', (message) => {
      setMessages((messages) => [...messages, message])
    })

    socket.on('roomData', ({ users }) => {
      setUsers(users)
    })
  }, [])

  const sendMessage = (event) => {
    event.preventDefault()

    if (message) {
      socket.emit('sendMessage', message, () => setMessage(''))
    }
  }

  return (
    <div className='outerContainer'>
      <div className='container'>
        <InfoBar room={room} />
        <Messages messages={messages} name={name} />
        <Input
          message={message}
          setMessage={setMessage}
          sendMessage={sendMessage}
        />
      </div>
      <TextContainer users={users} />
    </div>
  )
}

export default Chat

 

infoBar.js에 내용을 추가한다.

import React from 'react'

import onlineIcon from '../../icons/onlineIcon.png'
import closeIcon from '../../icons/closeIcon.png'

import './InfoBar.css'

const InfoBar = () => {
  ;<div className='infoBar'>
    <div className='leftInnerContainer'>
      <img className='onlineIcon' src={onlineIcon} alt='online icon' />
      <h3>{room}</h3>
    </div>
    <div className='rightInnerContainer'>
      <a href='/'>
        <img src={closeIcon} alt='close icon' />
      </a>
    </div>
  </div>
}

export default InfoBar

 

위 코드의 png를 적용하기 위해 사진을 넣어줄 것이다.

 

코드안의 경로와 맞게 src에 icons라는 폴더를 만들어 이미지를 넣어주면 된다. 

 

이미지.zip
0.00MB

 

input.js에 내용을 추가한다.

import React from 'react'

import './Input.css'

const Input = ({ setMessage, sendMessage, message }) => (
  <form className='form'>
    <input
      className='input'
      type='text'
      placeholder='전송하려는 메세지를 입력하세요.'
      value={message}
      onChange={({ target: { value } }) => setMessage(value)}
      onKeyPress={(event) =>
        event.key === 'Enter' ? sendMessage(event) : null
      }
    />
    <button className='sendButton' onClick={(e) => sendMessage(e)}>
      전송
    </button>
  </form>
)

export default Input

 

Messages폴더 안의 Messages.js에 내용을 추가한다.

import React from 'react'

import ScrollToBottom from 'react-scroll-to-bottom'

import Message from './Message/Message'

import './Messages.css'

const Messages = ({ messages, name }) => (
  <ScrollToBottom className='messages'>
    {messages.map((message, i) => (
      <div key={i}>
        <Message message={message} name={name} />
      </div>
    ))}
  </ScrollToBottom>
)

export default Messages

 

Messages폴더 안의 Message의 Message.js에 내용을 추가한다.

import React from 'react'

import './Message.css'

import ReactEmoji from 'react-emoji'

const Message = ({ message: { text, user }, name }) => {
  let isSentByCurrentUser = false

  const trimmedName = name.trim().toLowerCase()

  if (user === trimmedName) {
    isSentByCurrentUser = true
  }

  return isSentByCurrentUser ? (
    <div className='messageContainer justifyEnd'>
      <p className='sentText pr-10'>{trimmedName}</p>
      <div className='messageBox backgroundBlue'>
        <p className='messageText colorWhite'>{ReactEmoji.emojify(text)}</p>
      </div>
    </div>
  ) : (
    <div className='messageContainer justifyStart'>
      <div className='messageBox backgroundLight'>
        <p className='messageText colorDark'>{ReactEmoji.emojify(text)}</p>
      </div>
      <p className='sentText pl-10 '>{user}</p>
    </div>
  )
}

export default Message

 

TextContainer.js에도 내용을 추가한다.

import React from 'react'

import onlineIcon from '../../icons/onlineIcon.png'

import './TextContainer.css'

const TextContainer = ({ users }) => (
  <div className='textContainer'>
    <div>
      <h1>
        실시간 채팅 프로그램{' '}
        <span role='img' aria-label='emoji'>
          💬
        </span>
      </h1>
      <h2>
        Created with React, Express, Node and Socket.IO{' '}
        <span role='img' aria-label='emoji'>
          ❤️
        </span>
      </h2>
      <h2>
        Try it out right now!{' '}
        <span role='img' aria-label='emoji'>
          ⬅️
        </span>
      </h2>
    </div>
    {users ? (
      <div>
        <h1>현재 채팅중인 사람들 : </h1>
        <div className='activeContainer'>
          <h2>
            {users.map(({ name }) => (
              <div key={name} className='activeItem'>
                {name}
                <img alt='Online Icon' src={onlineIcon} />
              </div>
            ))}
          </h2>
        </div>
      </div>
    ) : null}
  </div>
)

export default TextContainer

 

모든 코드를 작성하면 

 

localhost:3000/ 에 이런 화면이 뜨고 가입을 누르면

 

 

채팅창이 뜬것을 확인할 수 있다.

 

다음 포스팅은 전체적인 채팅방 동작과 코드설명을 할 것이다.

728x90
반응형

+ Recent posts