리액트

[리액트] 실시간 채팅 구현하기 (socket.io, Nodejs) - 2

MC류짱 2023. 1. 27. 15:57

https://ryuwc.tistory.com/233

 

[리액트] 실시간 채팅 구현하기 (socket.io, Nodejs) - 1

https://www.youtube.com/watch?v=ZwFA3YMfkoc 위 영상을 기반으로 제작합니다. Client 세팅 (CRA) create-react-app을 통해 client 쪽을 세팅한다. 서버는 Nodejs express를 사용할 것이다. 리액트 라우터를 설치해주고 Join

ryuwc.tistory.com

1탄에 이어 글을 작성한다.

 

Client

먼저, 아래의 라이브러리들을 설치해준다.

npm i query-string
npm i socket.io-client
클라이언트 에서 socket 설치

npm i react-emoji
이모지를 사용하기위해 설치

npm i react-scroll-to-bottom
버튼을 누르면 최신으로 이동하기 위해 설치

이때 현재 최신의 socket버전과 영상의 버전 차이가 있기 때문에 오류가 뜬다.

socket버전을 client, server 둘 다 아래와 같이 package.json에서 버전을 내려준다.

node_module 충돌이 날 경우 노드모듈 폴더를 삭제하고 다시 npm i 를 터미널에서 친다.

 

유튜브의 내용을 따라갈 것이기 때문에 편의상 css는 아래의 파일을 사용한다.

 

Chat.css
0.00MB
Join.css
0.00MB

Join.js의 내용을 다음과 같이 작성한다.

import React, {useState} from 'react';
import {Link} from "react-router-dom";

import './Join.css';

function Join() {
  const [name, setName] = useState('')
  const [room, setRoom] = useState('')
  return (
    <div className='joinOuterContainer'>
      <div className='joinInnerContainer'>
        <h1 className='heading'></h1>
        <div>
          <input
            placeholder='이름'
            className='joinInput'
            type='text'
            onChange={(event) => setName(event.target.value)}
          />
        </div>
        <div>
          <input
            placeholder='채팅방'
            className='joinInput mt-20'
            type='text'
            onChange={(event) => setRoom(event.target.value)}
          />
        </div>
        <Link
          onClick={(e) => (!name || !room ? e.preventDefault() : null)}
          to={`/chat?name=${name}&room=${room}`}
        >
          <button className={'button mt-20'} type='submit'>
            가입
          </button>
        </Link>
      </div>
    </div>
  );
}

export default Join;

 

components 폴더안에 

InfoBar, Input, Messages, TextContainer 폴더를 생성하고 다음과 같이 만들어준다.

 

css 파일은 편의를 위해 아래의 첨부 파일을 사용한다.

InfoBar.css
0.00MB
Input.css
0.00MB
Messages.css
0.00MB
Message.css
0.00MB
TextContainer.css
0.00MB

생성한 컴포넌트들에 일단 다음과 같이 구별할 수 있게만 간단히 작성해준다.

import React from 'react';

import './InfoBar.css';

function InfoBar() {
  return (
    <div>
      인포바
    </div>
  );
}

export default InfoBar;

 

이제 Chat.js 파일을 다음과 같이 작성한다.

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

import './Chat.css';
import InfoBar from "../components/InfoBar/InfoBar";
import Messages from "../components/Messages/Messages";
import TextContainer from "../components/TextContainer/TextContainer";
import Input from "../components/Input/Input";

const ENDPOINT = 'http://localhost:5000';
let socket;

function Chat() {
  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(window.location.search);
    socket = io(ENDPOINT);
    setName(name);
    setRoom(room);
    socket.emit('join', {name, room}, (err) => {
      if (err) {
        alert(err);
      }
    });
    return () => {
      socket.emit('disconnect');
      socket.off();
    }
  }, [ENDPOINT, window.location.search]);

  useEffect(() => {
    socket.on('message', (message) => {
      setMessages([...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;

 

Server

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 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('새로운 유저가 접속했습니다.')
  socket.on('join', ({name, room}, callback) => {})
  socket.on('disconnect', () => {
    console.log('유저가 나갔습니다.')
  })
})

server.listen(PORT,()=>console.log(`서버가 ${PORT} 에서 시작되었어요`))

 

server 폴더 내에 router.js 파일을 생성하여 다음과 같이 작성한다.

const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
  res.send({ response: "I am alive" }).status(200)
})

module.exports = router

cors 문제를 해결하기 위해 cors와 실시간으로 채팅이 보여지도록 nodemon을 설치해 준다.

server 에서 설치

npm i cors
npm i -D nodemon

package.json에 start 부분을 추가한다. 

 

npm run start를 실행해 서버를 돌린다.

 

이제 Join에서 이름과 채팅방을 입력하며 Chat으로 접속하면 server터미널에 다음과 같이 뜰 것이다.

 

현재 Chat 페이지는 다음과 같다.

 

다음편에 이어서 작성하겠다.