React+Express+Socketio를 이용한 채팅구현

시작하기

socket은 실시간으로 사용자와 상호작용을 하는 경우 사용합니다.
가장 대표적인 예시로 채팅을 들수있습니다.

Socket.io

하나의 서버에서 정보를 내보내고 해당 정보를 다수의 클라이언트에서 받습니다.

초기 설정

백엔드 서버를 먼저 설정하고 리액트 프로젝트를 설정하겠습니다.

Node.js + Express

터미널
1
2
3
4
5
6
7
mkdir socket-backend // 서버 폴더 생성
cd socket-backend // 서버폴더로 이동

npm init // npm package 초기 설정
npm install --save express socket.io // 패키지 설치

touch server.js // server.js파일 생성

http.createServer를 사용하여 서버를 만듭니다. 다음으로 서버의 인스턴스가있는 io를 소켓으로 설정합니다. 마지막으로 서버는 포트 4002에서 수신 대기하도록 설정했습니다.

server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');

// localhost 포트 설정
const port = 4002;

const app = express();

// server instance
const server = http.createServer(app);

// socketio 생성후 서버 인스턴스 사용
const io = socketIO(server);

// socketio 문법
io.on('connection', socket => {
console.log('User connected');
socket.on('disconnect', () => {
console.log('User disconnect');
});
});

server.listen(port, () => console.log(`Listening on port ${port}`))

메세지를 입력받고 반환하는 부분을 추가하겠습니다.

server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...

// socketio 문법
io.on('connection', socket => {
socket.on('send message', (item) => {
const msg = item.name + ' : ' + item.message;
console.log(msg);
io.emit('receive message', {name:item.name, message:item.message});
});
socket.on('disconnect', function () {
console.log('user disconnected: ', socket.id);
});
});
// ...

서버프로젝트 터미널에서 node server.js를 실행해줍니다.
Listening on port 4002값으로 리턴됩니다.

React Component

create-react-app을 통해 시작하겠습니다.

1
2
3
4
create-react-app socket-app --typescript
cd socket-app

npm install --save socket.io-client @types/socket.io-client

socket.io-client 패키지를 설치해줍니다.

컴포넌트 스타일 작성

컴포넌트와 스타일을 작성해줍니다.
컴포넌트

App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, { ChangeEvent, FormEvent } from 'react';
import './App.css';

interface Message { name: string, message: string }
const App: React.FC = () => {
const [messageList, setMessageList] = React.useState<Message[]>([]);
const [value, setValue] = React.useState('');
const submit = (e:FormEvent<HTMLFormElement>) => {
e.preventDefault();
}
return (
<div className="App">
<section className="chat-list">
<div className="message">
<p className="username">username</p>
<p className="message-text">message</p>
</div>
</section>
<form className="chat-form"
onSubmit={(e:FormEvent<HTMLFormElement>) => submit(e)}>
<div className="chat-inputs">
<input
type="text"
autoComplete="off"
onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
value={name}
placeholder="유저이름"
/>
<input
type="text"
autoComplete="off"
onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
value={value}
placeholder="메세지입력하기"
/>
</div>
<button type="submit">입력하기</button>
</form>
</div>
);
}

export default App;
App.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
.chat-form {
display: flex;
height: 60px;
justify-content: space-between;
align-items: center;
padding: 10px;
box-sizing: border-box;
}
.chat-form > .chat-inputs {
flex: 8;
display: flex;
height: 100%;
}
.chat-inputs > input {
flex: 1;
height: 100%;
border: 1px solid rgb(150, 150, 150);
font-size: 18px;
outline: none;
transition: border 0.2s;
border-radius: 5px;
margin: 0 5px;
padding: 5px;
box-sizing: border-box;
}
.chat-inputs > input:focus {
border: 1px solid rgb(240, 119, 59);
}
.chat-form > button {
flex: 2;
margin-left: 10px;
border: 0px;
background: rgb(240, 119, 59);
width: 100px;
height: 100%;
border-radius: 5px;
outline: none;
cursor: pointer;
color: white;
font-size: 18px;
transition: background-color 0.2s;
}
.chat-form > button:active {
background-color: rgb(280, 119, 59);
}

.chat-list {
padding: 0 10px;
box-sizing: border-box;
}

.message {
min-height: 40px;
margin: 5px 0;
}
.message > .username {
margin: 0 0 5px 0;
font-size: 14px;
}
.message > .message-text {
margin: 0;
font-size: 18px;
color: white;
width: 100%;
display: flex;
align-items: center;
padding: 10px;
box-sizing: border-box;
background-color: rgb(240, 119, 59);
min-height: 40px;
border-radius: 10px;
}

연동하기

유저의 이름과 메세지를 send message로 소켓서버에 보내주고
receive message로 message값을 가져왔습니다.

App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, { ChangeEvent, FormEvent } from 'react';
import './App.css';
import socketIOClient from "socket.io-client";

interface Message { name: string, message: string }
const App: React.FC = () => {
const [messageList, setMessageList] = React.useState<Message[]>([]);
const [name, setName] = React.useState('');
const [value, setValue] = React.useState('');
const socket = socketIOClient('localhost:4002');
const submit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
socket.emit('send message', { name: name, message: value });
};
React.useEffect(() => {
socket.on('receive message', (message: { name: string, message: string }) => {
setMessageList(messageList => messageList.concat(message));
})
}, []);

return (
<div className="App">
<section className="chat-list">
{messageList.map((item: Message, i: number) =>
<div key={i} className="message">
<p className="username">{item.name.toUpperCase()}</p>
<p className="message-text">{item.message}</p>
</div>
)}
</section>
<form className="chat-form"
onSubmit={(e: FormEvent<HTMLFormElement>) => submit(e)}>
<div className="chat-inputs">
<input
type="text"
autoComplete="off"
onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
value={name}
placeholder="유저이름"
/>
<input
type="text"
autoComplete="off"
onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
value={value}
placeholder="메세지입력하기"
/>
</div>
<button type="submit">입력하기</button>
</form>
</div>
);
}

export default App;

결과물

Share