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 단계의 코드는 해당 컴포넌트를 컨테이너화하는 코드라는 것은 이제 알았다.
댓글