리액트와 socket.io, node.js를 활용하여 간단하게 채팅기능을 구현 해 보았습니다.
클라이언트 구현하기
리액트 컴포넌트 구조
각각의 메세지는 message컴포넌트, message들과 입력창을 담은 messages컴포넌트, messages를 담은 chatBox컴포넌트로 제작하였습니다.
소켓 설치방법
npm i socket.io-client
npm으로 설치한 후 소켓을 설정합니다.
클라이언트 소스코드
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app.jsx';
import { io } from 'socket.io-client';
const socket = io('http://localhost:8080', { autoConnect: false });
socket.connect();
ReactDOM.render(
<React.StrictMode>
<App socket={socket} />
</React.StrictMode>,
document.getElementById('root')
);
컴포넌트를 호출하는 최상단인 index.js에서 소켓을 설정하고 하위 컴포넌트로 넘겨줍니다.
const socket = io('http://localhost:8080', { autoConnect: false });
여기서 io()안의 주소는 서버주소를 입력해야 합니다. 8080번 포트에서 서버를 구동시킬것입니다.
소켓 연결을 설정한 후 connect()로 연결시켜줍니다. ( 서버를 작성하지 않으면 연결되지 않습니다.)
//app.jsx
import './app.css';
import ChatBox from './components/chatBox';
function App({ socket }) {
return <ChatBox socket={socket} />;
}
export default App;
app에서는 chatBox컴포넌트만 불러옵니다. 이 때 상위에서 받은 socket을 chatBox에 넘겨줍니다.
//chatBox.jsx
import React, {useState } from 'react';
import Footer from './footer';
import Header from './header';
import Messages from './messages';
import styles from './chatapp.module.css';
const ChatBox = ({ socket }) => {
const [messages, setMessages] = useState([]);
socket.on('updated', (msg) => setMessages(msg));
const sendMessage = (message) => {
setMessages((messages) => [
...messages,
{ id: Date.now(), username: socket.id, text: message },
]);
};
return (
<div className={styles.chatbox}>
<Header />
<Messages messages={messages} sendMessage={sendMessage} socket={socket} />
<Footer />
</div>
);
};
export default ChatBox;
채팅 메세지들을 담을 배열을 state로 선언 해 주었습니다. 새로운 채팅이 업데이트 되면 자동으로 컴포넌트가 렌더링 되도록 하고싶기 때문입니다.
이름을 잘 못 지은 함수 sendMessage가 있습니다.
sendMessage 함수는 새 메세지를 전달 받으면 messages state 에 새로운 메세지를 추가합니다.
각 메세지의 구조는 다음과 같습니다.
{ id: Date.now(), username: socket.id, text: message }
각 채팅마다의 id가 꼭 필요한건 아니지만.. 일단 지정해봤습니다.
uesrname은 추후 사용자가 직접이름을 입력하도록 변경할 예정인데 현재는 소켓의 id를 담고있습니다.
text 는 메세지의 내용입니다.
//messages.jsx
import React, { useRef } from 'react';
import Message from './message';
import styles from './chatapp.module.css';
const Messages = ({ socket, messages, sendMessage }) => {
const messageRef = useRef();
const formRef = useRef();
const onMessage = (event) => {
event.preventDefault();
sendMessage(messageRef.current.value);
socket.emit('newchat', messages, {
id: Date.now(),
username: socket.id,
text: messageRef.current.value,
});
formRef.current.reset();
};
return (
<div className={styles.messages_area} >
<ul className={styles.messages}>
{messages.map((message) => (
<Message message={message} socket={socket} />
))}
</ul>
<form ref={formRef} onSubmit={onMessage}>
<input ref={messageRef} type='text' className={styles.inputform} />
</form>
</div>
);
};
export default Messages;
messages에 있는 input으로 메세지를 작성하고 엔터를 누르면 onMessage()함수를 실행합니다.
onMessage에선 새로운 메세지를 앞서 우리가 정했던 메세지의 구조로 만든 다음 newchat이라는 소켓 이벤트를 생성해서 서버로 보내줍니다.
이 함수 안에서도 sendMessage함수가 사용되는 이유는 채팅을 보낸 작성자에게도 이 메세지가 바로 업데이트되는것을 보여주기 위함입니다.
//message.jsx
import React from 'react';
import styles from './chatapp.module.css';
const Message = ({ message, socket }) => {
const user = message.username === socket.id ? styles.me : styles.others;
return <li className={`${styles.msgbox} ${user}`}>{message.text}</li>;
};
export default Message;
각 메세지를 담고있는 최하위 컴포넌트입니다. 메세지의 socket.id가 내가 현재 접속중인 소켓 id와 같으면 내가 보낸 메세지이므로 class를 me로 지정하고 그렇지 않다면 others를 지정해줍니다.
내 메세지는 우측 정렬, 파란색으로 지정하고 상대방 메세지는 좌측정렬,회색으로 지정하기 위한 코드입니다.
여기까지 클라이언트의 코드가 끝이 났습니다.
컴포넌트를 분리시켜두어서 코드가 많아보이지만.. 사실 내용은 간단합니다.
서버 구현하기
서버는 매우 간단하게 app.js 파일안에서 작성하였습니다.
소켓 설치하기
서버에서도 클라이언트에서 해 주었던것 처럼 소켓을 설치 해야합니다. 다만 차이점은 클라이언트는 socket.io-client 였던 반면 서버는 그냥 socket.io으로 설치해주면 됩니다.
npm i socket.io
익스프레스 설치하기
npm i express
간단하게 웹서버를 구현하기 위해서 익스프레스를 사용하였습니다.
서버 소스코드
// server/app.js
import express from 'express';
import { Server } from 'socket.io';
const app = express();
const server = app.listen(8080);
console.log('서버시작');
const io = new Server(server, { cors: { origin: 'http://localhost:3000' } });
let msg = [];
io.on('connection', (socket) => {
console.log(`socket connectied 사용자 아이디 :${socket.id}`);
socket.on('newchat', (messages, chat) => {
msg = [...messages, chat];
console.log(msg);
socket.broadcast.emit('updated', msg);
});
});
필요한 모듈들을 임포트 해주고 express로 포트번호 8080을 사용하는 서버를 만들어줍니다.
이후 소켓의 new Server를 이용해서 우리가 만들어주었던 웹 서버에 소켓 서버를 생성합니다.
클라이언트가 3000번 포트에서 동작하기 때문에 cors옵션으로 3000을 지정해줍니다.
여기에 작성된 도메인에서만 우리의 서버로 소켓 접근이 가능합니다.
생성한 소켓서버에 이벤트들을 등록해주면 끝입니다.
아까 클라이언트의 messages컴포넌트에서 새 메세지를 작성하면 newchat이라는 소켓 이벤트를 발생시켰습니다.
//messages.jsx
...
const onMessage = (event) => {
event.preventDefault();
sendMessage(messageRef.current.value);
socket.emit('newchat', messages, {
id: Date.now(),
username: socket.id,
text: messageRef.current.value,
});
formRef.current.reset();
};
...
여기서 발생한 이벤트를 서버에서 듣습니다.
// server/app.js
...
socket.on('newchat', (messages, chat) => {
msg = [...messages, chat];
console.log(msg);
socket.broadcast.emit('updated', msg);
});
newchat이라는 이벤트가 발생하면 전달받은 채팅 메세지들과 새 메세지를 배열에 저장한 후
updated라는 새로운 이벤트를 발생시킵니다.
이 메세지는 클라이언트인 chatBox에서 듣습니다.
// chatBox.jsx
...
const [messages, setMessages] = useState([]);
socket.on('updated', (msg) => setMessages(msg));
...
새롭게 전달받은 배열을 현재 message state로 업데이트 해 줍니다.
간단하게 구동하는데 글로 쓰니까 더 어려워진 느낌이네요..
궁금하시거나 잘못된부분이 있으면 댓글로 알려주세요!