ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ์ป์ด๊ฐ ์ง์๊ณผ ๊ฒฝํ์ ๋ช๊ฐ ์ ๋ฆฌํด๋ณธ๋ค. ๋ ์ค๊ฐ ๋ง์ด ์๋ ค์ฃผ๊ณ ๋์์ฃผ์๋ค.
1. ๋ฆฌ๋์ค ํ์ฉ
์ฒ์ ๋ฆฌ๋์ค๋ฅผ ๊ณต๋ถํ ๋ ์ฝ์๋ ์ฑ ์์ Ducks ํจํด์ ๋ฐฐ์ ๋ค. ๋ง์ ์จ๋ณด๋ ค๋๊น ๊ดํ ๋ณต์กํ ๋๋์ด ๋ค์ด์ ๊ทธ ํ๋ก ๋ฆฌ๋์ค๋ฅผ ๋ณ๋ก ์ ์ข์ํ๋... ๊ธฐ์ต์ด.. ์ด๋ฒ์ ์ฐ๊ฒ๋ ๊ตฌ์กฐ๋ ์๋์ ๊ฐ๋ค. ๋ฆฌ๋์ค ๊ณต์ ์ฌ์ดํธ์ ์ ํ์๋ ๊ฒ๊ณผ ๋น์ทํ ๊ฑธ? ์ก์ ํ์ , ์ก์ ์์ฑํจ์ ๊ทธ๋ฆฌ๊ณ ๋ฆฌ๋์๋ฅผ ๊ฐ๊ฐ ํ๋์ ํด๋์ ๋ด๋๋ค. ํ๊ฐ์ง ๊ธฐ๋ฅ์ ์์ ํ ์ผ์ด ์๊ฒผ์ ๋ 3๊ฐ์ ํ์ผ์ ๋ชจ๋ ๊ฑด๋๋ ค์ผ ํ๋ค๋ ๋จ์ ์ผ๋ก ์ด์ฉ๊ตฌ์ ์ฉ๊ตฌ ํ๋ ๊ธ๋ค์ ๋ง์ด ๋ดค๋๋ฐ, ํ์ผ ํ๋์ ํ์ , ์ก์ , ๋ฆฌ๋์ ๋ค ๋๋ ค๋ฐ์์ ์ฝ๋ ๊ธธ์ด์ง๋๊ฒ ๋ ์คํ๋ ค ๋ถํธํ๋ค. ๊ฐ์ธ์ ์ผ๋ก๋ ๋ ๋ง์์ ๋๋ ๋ฐฉ์์ด๋ค.
reducer/index.js
์ ๋ถ๋ฌ์จ ๊ฐ ํ์ผ๋ค์ combineReducer๋ก ๋ฌถ์ด์ exportํ๋ ์ญํ ๋ง ํ๊ณ
storeSetting.js
์์ ๋ฐ๋ธํด์ฆ, thunk, createStore ๋ฑ์ ๋ค๋ฅธ ์ธํ
๋ค์ ํด์ค๋ค.
// ์ธ์ฆ๋ ์ํ์ผ๋ ๊ฐ์ ๋ก accessToken ๋ถ๋ฌ์ค๋ ์๋ฆฌ(initial ๋ฆฌ๋์ค ์ ์ง์ด๋ฃ๊ธฐ ์ํจ (์๋ก๊ณ ์นจ ์))
const userAccessToken = localStorage.getItem('userAccessToken');
const phoneNumber = localStorage.getItem('phoneNumber');
// axios Bearer ํ ํฐ์ ์ปค๋จผ ํค๋๋ก ๊ปด๋๊ธฐ ์ํจ
axios.defaults.headers.common.Authorization = `Bearer ${userAccessToken}`;
/**
* ...
**/
export const store = createStore(
reducers,
{
// initial state for autheticated
auth: {
authenticated: userAccessToken === null ? false : true,
userAccessToken: userAccessToken === null ? null : userAccessToken,
phoneNumber: phoneNumber === null ? null : phoneNumber
},
routePagination: {
currentPage: firstPathName
}
},
enhancer
);
storeSetting.js ์ ์ผ๋ถ์ด๋ค. ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์๋ ๊ฐ๋ค์ ๊ฐ์ ธ์์ initial state์ ๋ฃ์ด๋๋ค. ํ์ผ์ index.js
์ import ์์ผ๋์๊ธฐ ๋๋ฌธ์ ๋งค๋ฒ ์๋ก๊ณ ์นจํ ๋๋ง๋ค ๋ฐ๋ก๋ฐ๋ก ์คํ๋๋ค.
์ธ์๊น์๋ ๊ฒ์ axios default๋ฅผ ์ค์ ํด์ฃผ๋ ๋ถ๋ถ์ด์๋ค. ์ด์ ์๋ ํ ํฐ์ด ํ์ํ ์์ฒญ์ ๋ณด๋ผ๋๋ง๋ค ๋งค๋ฒ ํค๋๋ฅผ ๋ฃ์ด์ ๋ณด๋์๋ค. ํค๋๋ฅผ ๋ํดํธ๋ก ๋ฃ์ด์ค ์ ์๋ค. ๊ทธ๊ฑธ ๋งค๋ฒ ํ์ด์ง๊ฐ ์๋ก๊ณ ์นจ๋ ๋๋ง๋ค ํด์ฃผ๊ธฐ ๋๋ฌธ์ ์์ฒญ์ ๋ฐ๋ก ํค๋๋ฅผ ๋ฃ์ด์ค ํ์๊ฐ ์๋ค.
2. react-router-dom , history ํ์ฉ
import {
MESSAGE_SEND_SUCCESS,
MESSAGE_SEND_PENDING,
MESSAGE_SEND_ERROR
} from '../action-types';
import axios from 'axios';
import history from '../../history';
export const messageSend =
({ phoneNumber }) =>
async dispatch => {
try {
/**
* ..
**/
// ์๋์ผ๋ก ๋ค์ ๋จ๊ณ๋ก ๋์ด๊ฐ๊ฒ ๋
history.push('/auth/validation');
} catch (e) {
//400 ~
dispatch({ type: MESSAGE_SEND_ERROR, payload: '๋ฉ์์ง ์ ์ก ์ค๋ฅ' });
}
};
์ก์
์์ฑํจ์์ ์๋ ํ ํ์ผ์ ๋ค๊ณ ์ ๋ณด์๋ค. '๋ค์์ผ๋ก ๊ฐ๊ธฐ' ๊ฐ์ ๋ฒํผ์ ๋ฌ๋ฆฐ ์ก์
์ด๋ค. ๋ฆฌ๋์ค thunk๋ฅผ ์ด์ฉํ๊ณ ์๊ณ , axios ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํ ํ์ history.push(...)
๋ก ๋ค์ ๋จ๊ณ๋ก ๋์ด๊ฐ๋๋ก ํ๋ค.
๋ณดํต์ useNavigate
๋ฑ์ ์ด์ฉํด์ ๋ผ์ฐํ
์ ํ๋๋ฐ, ์์ ํจ์๋ ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋ด์์ ์ฐ์ด๋๊ฒ ์๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ ์ ์๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก history ๊ฐ์ฒด๋ฅผ ์ค์ ์ ํด์ค ๋ค์์ ๊ฐ์ ธ์์ ์ฌ์ฉํด์ฃผ์ด์ผ ํ๋ค.
import history from '../../history'
๊ฐ ๋ฐ๋ก ๊ทธ ๋ถ๋ถ์ด๋ค.
import { createBrowserHistory } from 'history';
import { routeChange } from './state/actions-creators';
import { store } from './state/storeSetting';
const history = createBrowserHistory();
history.listen(({ action, location }) => {
console.log(action, location);
store.dispatch(routeChange({ pathname: location.pathname }));
});
export default history;
history.js ์ด๋ค. createBrowserHistory()
๋ก ํ์คํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ ,
import React from 'react';
/**
* ..
**/
import history from './history';
import {
Routes,
Route,
unstable_HistoryRouter as HistoryRouter
} from 'react-router-dom';
/**
* ..
**/
ReactDOM.render(
<HistoryRouter history={history}>
<Provider store={store}>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/*" element={<Pagination />} />
</Routes>
</Provider>
</HistoryRouter>,
document.getElementById('root')
);
index.js ๋ก ๊ฐ์ ธ์ import ํ๋ค. BrowserRouter๊ฐ ์๋ HistoryRouter๋ก ์ปดํฌ๋ํธ๋ค์ ๊ฐ์ธ๊ณ , history ๊ฐ์ฒด๋ฅผ ์ ๋ฌํด์ค๋ค. ์ฐธ๊ณ
์ด๋ ๊ฒ ํ๋ฉด history๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
3. HOC ํ์ฉ
์ธ์ฆ์ด ๋์ด์์ ๋๋ง ๋ค์ด๊ฐ ์ ์๋ ํ์ด์ง์ ์ ๊ทผ์ ๋ง์ ๋ฐฉ๋ฒ์ผ๋ก HOC๋ฅผ ์ด์ฉํ๋ค.
import React from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import authPassHOC from '../../hoc/authPassHOC';
import requireAuth from '../../hoc/requireAuth';
import ListLandingPage from './ListLandingPage/ListLandingPage';
import TicketListPage from './TicketListPage/TicketListPage';
function ListProcess({ location }) {
// ์ธ์ฆ๋์ด์์ผ๋ฉด ๋๋ฉํ์ด์ง ๋๊ธฐ๊ณ ๋ฐ๋ก ํฐ์ผ๋ฆฌ์คํธ๋ก
const APHTicketListPage = authPassHOC(ListLandingPage, '/list/mytickets');
// ์ธ์ฆ ์๋์ด์์ผ๋ฉด ์ธ์ฆํ์ด์ง๋ก,
const RATicketListPage = requireAuth(TicketListPage);
return (
<Routes location={location}>
<Route
exact
path="landing"
element={<APHTicketListPage style={{ position: 'absolute' }} />}
/>
<Route
exact
path="mytickets"
element={<RATicketListPage style={{ position: 'absolute' }} />}
/>
{/*์ด์ํ url๋ก ๋ค์ด์ฌ๊ฒฝ์ฐ ํํ์ด์ง๋ก ์ด๋*/}
<Route path="*" element={<Navigate to="/" />} />
</Routes>
);
}
export default ListProcess;
์๋งคํ ํฐ์ผ์ ๋ณผ ์ ์๋ ํ์ด์ง๋ฅผ ์ํ ์ปดํฌ๋ํธ๋ค์ ๊ด๋ฆฌํ๋ ํจ์์ด๋ค. 'list/landing'์ ์ธ์ฆ์ด ๋์ด ์์ง ์์ ๋๋ง ๋ณด์ฌ์ค์ผ ํ๊ณ 'list/mytickets'๋ ์ธ์ฆ์ด ๋์ด์์ ๋๋ง ๋ณด์ฌ์ค์ผ ํ๋ค. ๋ผ์ฐํธ ์์์ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ๊ธฐ ์ ์ hoc๋ก ํ๋ฒ ๊ฐ์ธ์ ์ธ์ฆ ์ฌ๋ถ๋ฅผ ํ์ธํ ํ์ ๋๊ฒจ์ค๋ค.
import React from 'react';
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
/**
* ์ธ์ฆ์ด ๋์ด์์ผ๋ฉด ๋ค์ ํ์ด์ง๋ก ๋๊น
**/
const authPassHOC =
(Component, nextUrl) =>
({ ...props }) => {
const { authenticated } = useSelector(state => state.auth);
return authenticated === true ? (
<Navigate to={nextUrl} />
) : (
<Component {...props} />
);
};
export default authPassHOC;
/**
* ์ธ์ฆ์ด ์๋์ด์์ผ๋ฉด ๋ฉ์ธ ํ์ด์ง๋ก ๋๋ ค๋ณด๋
**/
const requireAuth =
Component =>
({ ...props }) => {
const { authenticated } = useSelector(state => state.auth);
return authenticated === true ? (
<Component {...props} />
) : (
<Navigate to="/" />
);
};
export default requireAuth;
hoc์ ๊ตฌ์กฐ๋ ์์ ๊ฐ๋ค. ๊ฐ๋จํ๋ค. ์คํ ์ด์์ ๊ฐ์ ๊ฐ์ ธ์ค๊ณ ์ธ์ฆ ์ ๋ฌด์ ๋ฐ๋ผ ๋ ๋๋งํ ์ปดํฌ๋ํธ๋ฅผ ์ ํด์ค๋ค.
์ด์ ์๋ ํ์ด์ง์ ์ง์ ํ ํ์ ์ธ์ฆ ์ ๋ฌด๋ฅผ ํ์ธํ๊ณ ๋ค์ ๋๋ ค๋ณด๋ด๋ ๋ฐฉ์์ผ๋ก๋ง ํ์๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ๊ธฐ ์ ์ hoc ์์์ ํ๋ฒ ๊ฒ์ฆ์ ๊ฑฐ์น๋ ๊ณผ์ ์ ํตํด ๋ ์์ฐ์ค๋ฌ์ด ํ๋ฆ์ ๊ฐ์ ธ๊ฐ ์ ์๋ค.