[React] 혼자 만들어본 채팅 웹 앱(클라이언트 부분)

이 포스트는 “[React] 혼자 만들어본 채팅 웹 앱”의 클라이언트 부분 설명입니다.

관련 글을 보고싶은 분은 글 최하단의 태그 Toy project 링크를 눌러 확인해보세요!

설명에 앞서 프론트를 전문으로 하는 개발자가 아니기 때문에 어색한 부분이 있을 수 있습니다.

특히 디자인 부분은.. 더이상의 설명은 생략하겠습니다 ㅜㅜ


서버와는 다르게 클라이언트는 코드가 긴편이다.

저장소 주소

설치

1
2
3
4
cd workspace
git clone https://github.com/dev-sawd/chat-react
cd chat-react
npm install

실행

1
npm start

설명

로그인

내가 만든 채팅 웹 앱은 첫 화면이 로그인 화면이다.

따라서 로그인 화면이 떴을때 서버와의 소켓 연결을 시도한다.

src/login.js
1
2
3
useEffect(() => {
SocketManager.setSocket(io.connect('http://localhost:4000'))
}, [])

서버에서는 io.on('connection', (socket) => {...} 이벤트가 호출된다.

src/login.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 로그인 버튼 onClick
onClick={() => {
if (userName === '')
return;
dispatch(setLoginUser(userName));
SocketManager.getSocket().emit('login', {userName});
SocketManager.getSocket().on('returnLoginResponse',
function(result, userNameList) {
if (result) {
dispatch(setUserNameList(userNameList));
navigate('/main');
} else {
alert('같은 아이디가 이미 존재합니다');
}
});
}}

입력한 ID가 공백이 아니라면 서버로 login 이벤트를 호출한다.

이후 서버로부터 returnLoginResponse 이벤트가 호출됐을때 채팅 메인페이지로 이동하거나 같은 아이디가 존재하는지 경고창을 보이는등의 처리를 한다.

지금 보니 returnLoginResponse 이벤트는 useEffect에서 지정하면 조금 더 좋을것 같다.

채팅 메인 페이지

스크린샷의 좌측 리스트 영역으로 로그인한 사용자 목록과 함께 가장 최근 리스트를 간략하게 보여준다.

우측은 간단한 웰컴 페이지와 함께 대화 상대를 선택하면 채팅 방으로 변경된다.

이 화면에서는 주로 state에 따라 UI를 변경하고 소켓으로부터 받은 정보를 Redux에 전달한다.

src/chat.js
1
2
3
4
5
6
7
8
9
10
11
12
13
useEffect(() => {
socket.on('message', (message) => {
dispatch(addMessage(message))
})

socket.on('loginUser', (userName) => {
dispatch(addUserName(userName))
})

socket.on('logoutUser', (userName) => {
dispatch(deleteUserName(userName))
})
}, [])
  • message
    서버에서 message 이벤트를 호출해 메세지를 수신하면 Redux로 저장한다.
  • loginUser
    서버에서 loginUser 이벤트를 호출해 추가로 로그인한 사용자 정보를 Redux로 저장한다.
  • logoutUser
    서버에서 logoutUser 이벤트를 호출해 로그아웃한 사용자 정보를 Redux에서 삭제한다.

위의 조합을 통해 채팅 목록과 유저 목록을 관리한다.

로그인 유저 목록(채팅 룸)

좌측 로그인 유저 목록을 관리하는 컴포넌트로 Redux에 저장된 usernameList를 통해 해당 목록을 보여준다.

src/components/chatRoomSide.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
userNameList.map((userName) => {
if (userName !== loginUser) {
return <ChatRoomBox key={userName} userName={userName}
lastMessage={messageList.filter(
message => ((message.sendUserName ===
loginUser &&
message.targetUserName === userName)
||
(message.sendUserName === userName &&
message.targetUserName ===
loginUser)))}
onClick={() => {
dispatch(setTargetUserName(userName));
}}/>;
} else {
return null;
}
})

채팅

유저 목록에서 채팅할 상대를 선택하면 우측 영역이 채팅 영역으로 변경된다.

1
2
3
4
5
6
props.messages.map((message, index) => {
if (message.sendUserName === loginUser)
return <MyMessageBox key={index} message={message.message}/>;
else
return <OtherMessageBox key={index} message={message.message}/>;
})

내가 보낸 메세지인지, 상대방으로 부터 받은 메세지인지를 판단해서 좌측 또는 우측에 메세지를 배치시킨다.

Redux

부모 컴포넌트에서 자식 컴포넌트로 반복적인 props 전달 및 state관리를 조금 더 편하게 하기 위해서 REDUX를 사용했다.

이 사이트의 Quick Start 부분을 그대로 따라해서 구조를 잡았다.

features/chatSlice.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
25
26
27
28
29
30
31
32
33
34
35
36
37
// 채팅 관련 state 관리
export const chatSlice = createSlice({
name: 'chat',
initialState: {
targetUserName: null,
userNameList: [],
messageList: [],
},
reducers: {
setTargetUserName: (state, action) => {
state.targetUserName = action.payload;
},

setUserNameList: (state, action) => {
state.userNameList = action.payload;
},

addUserName: (state, action) => {
state.userNameList = [...state.userNameList, action.payload];
},

deleteUserName: (state, action) => {
state.userNameList = state.userNameList.filter(function(userNameElement) {
return userNameElement !== action.payload;
});
},

setMessageList: (state, action) => {
state.messageList = action.payload;
},

addMessage: (state, action) => {
state.messageList = [...state.messageList, action.payload];
},

},
});

채팅과 관련된 state를 관리한다.

예를들면 채팅 가능한 상대들의 목록이나 채팅 상대 지정, 메세지 목록등의 정보를 저장한다.

처음부터 더 고민해서 만들었다면 조금 더 일관적인 구조가 되었을것 같다.
코드 자체는 간단하지만 구조에 기준이 없는 조잡한느낌..

features/loginSlice.js
1
2
3
4
5
6
7
8
9
10
11
12
// 로그인한 내 정보 관리
export const loginSlice = createSlice({
name: 'loginUser',
initialState: {
user: null,
},
reducers: {
setLoginUser: (state, action) => {
state.user = action.payload
},
},
})

로그인한 내 정보를 관리한다.

이 정보를 이용해 유저 목록에서 내 정보를 가장 위에 배치하거나 내가 보낸 메세지인지 받은 메세지인지를 구분하는등의 정보로 사용한다.

정리

이곳저곳 더 신경쓰거나 DB를 추가한다면 더 괜찮은 채팅 웹 앱이 될 수 있다고 생각한다.

이 프로젝트 개발하면서 또 다른것을 만들어야겠다는 생각이 들었던 만큼 재미있는 프로젝트였다.