본문 바로가기
코드리뷰

[카카오톡 클론코딩]2022.08.06

by 치우치지않는 2022. 8. 7.

3. MenuContainer

import React, { Component } from 'react';
import styled from 'styled-components';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { Dispatch, bindActionCreators } from 'redux';
import { Socket } from 'socket.io-client';
import { MenuRoute } from '~/routes';
import { MenuSideBar } from '~/components/menu';
import { AuthActions } from '~/store/actions/auth';
import { UserActions } from '~/store/actions/user';
import { ChatActions } from '~/store/actions/chat';
import { RootState } from '~/store/reducers';
import { PAGE_PATHS } from '~/constants';
import { Auth } from '~/types/auth';
import { ProfileContainer, ChattingRoomContainer } from '~/containers';
import { ChattingResponseDto, UpdateRoomListDto } from '~/types/chatting';

const Wrapper = styled.main`
width: 100%;
display: flex;
`;

interface Props {
rootState: RootState;
authActions: typeof AuthActions;
userActions: typeof UserActions;
chatActions: typeof ChatActions;
}

class MenuContainer extends Component<Props> {
constructor(props: Props) {
super(props);
const auth: Auth | undefined = props.rootState.auth.auth;
if (auth) {
const socket = props.rootState.auth.socket as typeof Socket;
props.userActions.fetchUser(auth.user_id);
props.userActions.fetchFriends(auth.id);
props.userActions.fetchRoomList(auth.id);
socket.emit('join', auth.id.toString());
socket.on('message', (response: ChattingResponseDto) => {
this.updateRooms(response);
});
}
}

updateRooms = async (response: ChattingResponseDto) => {
const userState = this.props.rootState.user;
const chatState = this.props.rootState.chat;
const roomList = userState.room_list;
const { fetchRoomList, updateRoomList } = this.props.userActions;

const findRoom = roomList.find(room => room.room_id === response.room_id);
if (findRoom) {
const haveReadChat = response.room_id === chatState.room_id;
const notReadChat = haveReadChat ? 0 : findRoom.not_read_chat + 1;
const lastReadChatId = haveReadChat
? response.id
: findRoom.last_read_chat_id;
const updateRoomObj: UpdateRoomListDto = {
room_id: response.room_id,
last_chat: response.message,
updatedAt: response.createdAt,
not_read_chat: notReadChat,
last_read_chat_id: lastReadChatId
};
updateRoomList(updateRoomObj);
} else {
await fetchRoomList(userState.id);
}
};

async componentDidUpdate(prevProps: Props) {
const chatState = this.props.rootState.chat;
if (prevProps.rootState.chat.room_id !== chatState.room_id) {
const socket = this.props.rootState.auth.socket as typeof Socket;
const { addChatting } = this.props.chatActions;
await socket.off('message');
await socket.on('message', async (response: ChattingResponseDto) => {
if (response.room_id === chatState.room_id) {
await addChatting(response);
}
await this.updateRooms(response);
});
}
}

render() {
const { logout } = this.props.authActions;
const authState = this.props.rootState.auth;
const token = authState.auth;
const socket = authState.socket as typeof Socket;
const chatState = this.props.rootState.chat;
const userState = this.props.rootState.user;
const roomList = userState.room_list;

// 로그인 상태가 아니라면 로그인 메뉴로 이동합니다.
if (!token) {
return <Redirect to={PAGE_PATHS.LOGIN} />;
}

return (
<React.Fragment>
<ProfileContainer />
{chatState.isChattingRoomShown ? <ChattingRoomContainer /> : null}
<Wrapper>
<MenuSideBar roomList={roomList} socket={socket} logout={logout} />
<MenuRoute />
</Wrapper>
</React.Fragment>
);
}
}

const mapStateToProps = (state: RootState) => ({
rootState: state
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
authActions: bindActionCreators(AuthActions, dispatch),
userActions: bindActionCreators(UserActions, dispatch),
chatActions: bindActionCreators(ChatActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchToProps)(MenuContainer);

그래.. 이제 또 난관에 부딪혔다.. 어디서부터 손을 봐야할까.. 우선 주석이 눈에 띄니까 보면 !token 인 경우 로그인 메뉴로 이동하게끔 만들고 있다. 그리고 return 하는 것을 보면, ProfileContainer, ChattingRoomContainer, MenuSideBar, MenuRoute 를 return 한다. Container 는 Containers 에 저장되어 있는 것들이고, MenuSideBar 는 components/menu 에서 가져온 것이다. 

-- 어제 쓰던 것 이어서 적자면,, 

class MenuContainer extends Component 부분은 일단 super() 개념을 까먹어서 다시 찾아 보았고, 

https://ko.javascript.info/class-inheritance

 

클래스 상속

 

ko.javascript.info

fetch 가 무엇인지, userAction, AuthAction, ChatAction 이란 무엇인지 좀 더 찾아봐야 할 것 같다. + 소켓도 ㅠㅠ 

https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch

 

Fetch 사용하기 - Web API | MDN

Fetch API는 HTTP 파이프라인을 구성하는 요청과 응답 등의 요소를 JavaScript에서 접근하고 조작할 수 있는 인터페이스를 제공합니다. Fetch API가 제공하는 전역 fetch() (en-US) 메서드로 네트워크의 리소

developer.mozilla.org

우선 간단하게나마 알게 된 것은 백에서 데이터를 json 형식으로 받아올 때 쓰이는 것이며 response 값을 가진다 정도. 

https://valuefactory.tistory.com/264

 

socket.io 모듈:socket.emit과 socket.on

・socket.io는 emit으로 송신한 정보를 on으로 받는 매우 단순한 구조이다! ・단 emit과 on의 소켓명이 동일 해야한다. 아래의 소스코드의 경우 서버에서 클라이언트로 정보를 송신할 때, news라는 이

valuefactory.tistory.com

socket 도 이 정도로 간단하게는 알아 놓았다. emit 으로 송신을 하고 on 으로 정보를 받아온다. 그리고 소켓의 이름이 같은 것 끼리만 정보를 주고 받을 수 있다. 고로 위 코드의 내용은 서버와 통신하는 내용이 주를 이루고 있고 message 라는 소켓을 받아와서 updateRooms를 이용해 Room 을 update 시켜주고 있다.

 

그렇다면 MenuContainer 안에 들어 있었던, updateRooms 를 보자. async 가 있는데, 비동기 관련해서 개념이 부족한 것 같아서 보충했다.

https://velog.io/@zayong/%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC

 

비동기처리

특정 로직의 실행이 끝날때까지 기다려주지 않고 나머지 코드를 먼저 실행하는것을 비동기 처리라고 한다. 비동기처리가 필요한 이유는 화면에서 서버로 데이터를 요청했을때 서버가 언제 그

velog.io

async 가 있다는 건 서버로부터 정보를 받아온다는 뜻이다. 그리고 response 로 ChattingResponseDto 를 받아오고 있다. 그리고 변수 세 개가 눈에 띄는데 바로 userState, chatState, roomList 이다. 그리고 findRoom과 updateRoomobj 를 정의하고 있다. 또한 haveReadChat 등이 있는 것을 보아 해당 컴포넌트는 채팅이 새로 들어왔을 때 채팅리스트를 업데이트 해주는 역할을 한다고 추정할 수 있을 것 같다.(그런데 여기서 헷갈리는 것이 이것이 Room 하나인지 Rooms 전체 리스트를 update 하는지 여부이다. 우선 후자쪽으로 생각이 기울어서 잠정적으로 이렇게 결론을 내리고 넘어간다.) 

 

componentDidUpdate 는 새로온 채팅이 있는지 없는지 검사하는 용도인 것 같다. (chatState)변수명으로 추론한 것이다. 이때 socket.off 가 쓰여서 찾아봤다. 대충 이벤트 리스너를 끄는 것으로 이해하고 넘어갔다. (off니까 on의 반대겠지 뭐..)

추가적으로 socket.on(eventName, eventListner) 이라는 것을 알게 되었다. 

여기서 이벤트 리스너가 무엇인지 몰라서 이것도 서치해 보았다. 분명 생활코딩 강의에서 여러번 들었는데 까먹었다..^^

https://runebook.dev/ko/docs/socketio/listening-to-events

 

Socket.IO - 이벤트 듣기 - 서버와 서버 간에 전송되는 이벤트를 처리하는 방법에는 여러 가지가 있

서버와 클라이언트간에 전송되는 이벤트를 처리하는 방법에는 여러 가지가 있습니다. 서버 측에서 Socket 인스턴스는 Node.js EventEmitter 클래스를 확장합니다 . 클라이언트 측에서 Socket 인스턴스는

runebook.dev

http://tcpschool.com/javascript/js_event_eventListenerRegister

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

다음으로 render 함수를 봤는데, rootState 가 계속 나오길래 도대체 rootState 가 뭘까해서 찾아봤다. 

interface Props {
rootState: RootState;
authActions: typeof AuthActions;
userActions: typeof UserActions;
chatActions: typeof ChatActions;
}

우선 props 에 있고, 이는 

import { RootState } from '~/store/reducers';

리듀서에 있다. 그렇다면 리듀서란 무엇이며 어떤 역할을 하는 것일까? 

https://velog.io/@zayong/Reducer%EB%9E%80

 

Reducer란

reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수이다.reducer 에서 반환하는 상태는 곧 컴포넌트가 지닐 새로운 상태가 된다.reducer 사용법여기서 state 는 앞

velog.io

간단히 말해, reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수이다.

render() {
const { logout } = this.props.authActions;
const authState = this.props.rootState.auth;
const token = authState.auth;
const socket = authState.socket as typeof Socket;
const chatState = this.props.rootState.chat;
const userState = this.props.rootState.user;
const roomList = userState.room_list;

그래서 여기 렌더의 이 것들은 모두 현재 state 를 저장 중이라고 보면 된다. 여기서 잠깐 render 의 정확한 의미도 짚고 넘어가자.

https://yceffort.kr/2022/04/deep-dive-in-react-rendering

 

Home

yceffort

yceffort.kr

요점만 말하자면, 리액트에서 렌더링이란, 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다. 라고 한다. 그렇다면 지금 MenuContainer 컴포넌트가 쓰일 때는 


return (
<React.Fragment>
<ProfileContainer />
{chatState.isChattingRoomShown ? <ChattingRoomContainer /> : null}
<Wrapper>
<MenuSideBar roomList={roomList} socket={socket} logout={logout} />
<MenuRoute />
</Wrapper>
</React.Fragment>
);
}

요것들이 반납되어 화면에 나타날 것이다.

 

const mapStateToProps = (state: RootState) => ({
rootState: state
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
authActions: bindActionCreators(AuthActions, dispatch),
userActions: bindActionCreators(UserActions, dispatch),
chatActions: bindActionCreators(ChatActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchToProps)(MenuContainer);

그리고 코드 최하단의 map 이 부분은 아직도 모르겠어서 간단히 서치를 해 보았다.  리덕스 관련 코드여서 몰랐던 것이다! 

우선, 맨 마지막에 export default 의 connect 함수는 컨테이너 컴포넌트를 만드는 또 다른 방법이다. (useSelector, useDispatch가 워낙 편하기 때문에 요즘엔 거의 쓰지 않는다고 한다.)

mapStateToProps 는 리덕스 스토어의 상태를 조회해서 어떤 것들을 props 로 넣어줄지 정의합니다.
mapDispatchToProps 는 액션을 디스패치하는 함수를 만들어서 props로 넣어줍니다. 디스패치를 파라미터로 받아온다.

그래 완벽히 이해가 가는 건 아니지만 어쨋든 저 3 단계의 코드는 해당 컴포넌트를 컨테이너화하는 코드라는 것은 이제 알았다. 

'코드리뷰' 카테고리의 다른 글

[카카오톡 클론코딩]2022.08.07  (0) 2022.08.08
[카카오톡 클론코딩]2022.08.05  (0) 2022.08.06
[카카오톡 클론코딩]2022.08.04  (0) 2022.08.04
[카카오톡 클론코딩]2022.08.03  (0) 2022.08.03
코드리뷰 - 카카오톡  (0) 2022.07.30

댓글