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라는 폴더를 만들어 이미지를 넣어주면 된다.
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/ 에 이런 화면이 뜨고 가입을 누르면
채팅창이 뜬것을 확인할 수 있다.
다음 포스팅은 전체적인 채팅방 동작과 코드설명을 할 것이다.