본문 바로가기
코드리뷰

[카카오톡 클론코딩]2022.08.05

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

src 의 index.tsx 가 root 파일인 것 같음. 여기서 App 을 import 하되 그 App 을 Provider 로 감싸고,  Provider 의 prop 으로 store 를 주고 있음. 

import React from "react";
import ReactDOM from "react-dom";
import {Provider} from 'react-redux';
import App from "./App";
import GlobalStyle from '~/styles/GlobalStyle';
import store from '~/store';
ReactDOM.render(
<React.Fragment>
<GlobalStyle/>
<Provider store={store}>
<App />
</Provider>
</React.Fragment>
,document.querySelector("#root"));

src 의 constant.ts

export enum PAGE_PATHS {
HOME = '/',
LOGIN = '/login',
SIGNUP = '/signup',
MENU = '/menu',
FRIENDS = '/menu/friends',
CHATTING = '/menu/chatting',
CHATTING_ROOM = '/room'
}

export const HOST = process.env.HOST || 'http://localhost:8001';

export const API_HOST = process.env.API_HOST || `${HOST}/api`;

export const BASE_IMG_URL = '/asset/base_profile.jpg';

constant.ts 는 변하지 않는 것들 정리해 둔 것. 

 

App.tsx 를 보자. 

import React, { Component } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect
} from 'react-router-dom';
import { Menu, Login, Signup } from '~/pages';
import { PAGE_PATHS } from '~/constants';

class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path={PAGE_PATHS.LOGIN} component={Login} />
<Route path={PAGE_PATHS.SIGNUP} component={Signup} />
<Route path={PAGE_PATHS.MENU} component={Menu} />
<Route
path={PAGE_PATHS.HOME}
component={() => <Redirect to={PAGE_PATHS.LOGIN} />}
/>
</Switch>
</Router>
);
}
}

export default App;

Router 이용해서 Route 경로 표시를 해주고 있고, 각각의 Route 에 component prop 으로 Login, Signup, Menu 페이지를 주고 있다. 그럼 각각의 Page 를 봐 보자. 

1. Login Page 

import React from 'react';
import styled from 'styled-components';
import { LoginContainer } from '~/containers';
const Wrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
background-color: #f5f6f7;
padding: 25px 0;
`;

const Login: React.FC = () => {
return (
<Wrapper>
<LoginContainer />
</Wrapper>
);
};

export default Login;

단순히 LoginContainer 를 Wrapper 로 감싸고 있다.

2. Signup Page
import React from 'react';
import styled from 'styled-components';
import { SignupContainer } from '~/containers';

const Wrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
background-color: #f5f6f7;
`;

const Signup: React.FC = () => {
return (
<Wrapper>
<SignupContainer />
</Wrapper>
);
};

export default Signup;

마찬가지로 SignupContainer 를 Wrapper 로 감쌀 뿐이며

3. Menu Page

 

import React from 'react';
import styled from 'styled-components';
import { MenuContainer } from '~/containers';

const Wrapper = styled.div`
width: 100%;
`;

const Menu: React.FC = () => {
return (
<Wrapper>
<MenuContainer />
</Wrapper>
);
};

export default Menu;

역시 마찬가지이다. 그럼 이제 각각의 Container 를 살펴 보자. 

 

+페이지에는 이 외에도 Modal.tsx 이랑 ChattingRoom.tsx 가 더 있다.

 

1. LoginContainer

import React, { Component } from 'react';
import styled from 'styled-components';
import { Dispatch, bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Header, Content, Footer } from '~/components/login';
import { AuthActions } from '~/store/actions/auth';
import { RootState } from '~/store/reducers';
import { AuthState } from '~/store/reducers/auth';
import { PAGE_PATHS } from '~/constants';

const Wrapper = styled.div`
width: 360px;
height: 600px;
background-color: #ffeb33;
`;
interface Props {
authActions: typeof AuthActions;
authState: AuthState;
}

class LoginContainer extends Component<Props> {
// 로그인 실패 메시지 등을 제거
componentWillUnmount() {
this.props.authActions.changeMessage('');
}

render() {
const { login, changeMessage } = this.props.authActions;
const { token, loginFailuerMsg, loggingIn } = this.props.authState;

const contentProps = {
login,
changeMessage,
loginFailuerMsg,
loggingIn
};
if (token) return <Redirect to={PAGE_PATHS.FRIENDS} />;
return (
<Wrapper>
<Header />
<Content {...contentProps} />
<Footer />
</Wrapper>
);
}
}
const mapStateToProps = (state: RootState) => ({
authState: state.auth
});

const mapDispatchToProps = (dispatch: Dispatch) => ({
authActions: bindActionCreators(AuthActions, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer);

보니 토큰 검사를 한 번 해준다. 그 후 토큰이 제대로 입력되었다면 <Header>, <Content>, <Footer> 를 반환한다. 이때 Content 에는 props 로 contentProps 가 전달된다. 그리고 이 Header, Content, Footer 는 어디에 있느냐면 components/login 에 있다. 

그 아래 mapStateToProps 랑 mapDispatchToProps 는 무슨 의미인지 아직 잘 모르겠다. 

 

2 SignupContainer

import React, { Component } from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Header, Content } from '~/components/signup';
import { RootState } from '~/store/reducers';
import { AuthState } from '~/store/reducers/auth';
import { PAGE_PATHS } from '~/constants';

const Wrapper = styled.div`
margin: 0 auto;
width: 50%;
min-height: 95vh;
border: 1px solid #dadada;
@media only screen and (max-width: 800px) {
width: 95%;
}
`;

interface Props {
authState: AuthState;
}

class SignupContainer extends Component<Props> {
render() {
const { token } = this.props.authState;
if (token) return <Redirect to={PAGE_PATHS.FRIENDS} />;

return (
<Wrapper>
<Header />
<Content />
</Wrapper>
);
}
}

const mapStateToProps = (state: RootState) => ({
authState: state.auth
});

const mapDispatchToProps = () => ({});

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

여기서도 token 검사를 한 번 해준 뒤에 Header,Content 를 돌려 보낸다. 마찬가지로 아래 두 코드는 아직 무슨 의미인지 잘 모르겠다. Header, Content 는 components/signup 에 있는 컴포넌트들이다. 

 

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 에서 가져온 것이다. 

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

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

댓글