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