
지난 번에 토큰을 쿠키에 저장하는 부분까지 구현해 보았다.
이번에는 로그인 기능뿐만 아니라 사용자가 다른 페이지로 이동하거나 페이지를 새로고침해도 로그인 상태를 유지할 수 있도록 구현해보려고 한다.
작년에 팀 프로젝트를 할 때 상태 관리 라이브러리로 Recoil을 사용해본 경험이 있어
이번 싸커 마켓에서는 Redux를 활용해 구현할 것이다.
🌐 공식 문서 : https://ko.redux.js.org/introduction/getting-started
Redux는 JavaScript 애플리케이션의 상태 관리를 위한 예측 가능하고 중앙 집중식인 상태 컨테이너 라이브러리이다.
애플리케이션의 상태를 예측 가능하고 효율적으로 관리할 수 있도록 도와주는 강력한 도구이며,
상태의 중앙 집중식 관리와 단방향 데이터 흐름을 통해 애플리케이션의 복잡성을 줄이고 유지 보수성을 향상시켜준다.
Redux 애플리케이션에서는 상태가 단일 Store에 저장됨
이 Store에 있는 상태가 애플리케이션의 전역 상태를 나타내며, 여러 컴포넌트가 이 상태에 접근 가능
애플리케이션의 상태를 중앙 집중식으로 관리하고, 상태의 일관성과 예측 가능성 보장
Redux에서는 상태를 직접 수정하는 것이 아니라 Action을 통해 상태를 변경함
상태를 변경하기 위해선 Action을 발생시켜야 하며, 이 Action은 Reducer에 의해 처리됨
상태의 불변성을 유지하고, 상태 변경을 추적하기 쉽게함
Redux에서 상태를 변경하는 로직은 순수 함수로 작성되어야함
순수 함수란 이전 상태와 Action을 받아서 새로운 상태를 반환하는 함수
순수 함수는 외부의 상태를 변경하지 않으며, 동일한 입력에 대해서 항상 동일한 출력 반환
예측 가능하고 테스트 가능한 상태 관리를 제공
Redux의 상태를 단일 Store에 저장하는 객체로
애플리케이션의 전역 상태를 보유하고 있으며, 상태를 읽고 업데이트 하는 메서드가 포함되어 있다.
Redux 애플리케이션에서 상태를 변경하기 위해 발생하는 이벤트로
일반적으로 JavaScript 객체로 표현되며, 반드시 type 필드를 가져야한다.
이전 상태와 Action을 받아 새로운 상태를 반환하는 순수 함수로
애플리케이션의 상태를 변경하는 로직을 포함하고 있으며, Store에 등록된다.
Redux에서는 여러 개의 Reducer를 조합하여 하나의 root Reducer를 만들어야한다.
Store에 Action을 보내는 메서드로
Action을 Dispatch하면 Store는 해당 Action을 받아서 Reducer에 전달하고, 상태를 업데이트한다.
Store의 상태 변경을 감지하고 이에 반응하는 콜백 함수를 등록하는 메서드로
Subscribe를 통해 등록된 콜백 함수는 상태가 변경될 때마다 실행된다.

- Redux 설정하기
- 상태(State) 정의하기
- 액션(Action) 정의하기
- 리듀서(Reducer) 정의하기
- Redux Store 생성하기
- Component와 Redux 연결하기
다음과 같은 순서대로 구현하였다.
먼저 Redux를 설치해준다.
npm install redux
isLogIn, token, memberType과 같은 사용자 인증 상태를 저장할 Redux 상태를 정의해준다.
const initialState = {
isLogIn: false,
token: null,
memberType: ''
}
const authReducer = ( state=initialState, action ) => {
switch(action.type) {
case LOGIN:
return {
...state,
isLogIn: true,
token: action.payload.token,
memberType: action.payload.memberType
}
case LOGOUT:
return {
...state,
isLogIn: false,
token: null,
memberType: ''
}
default:
return state;
}
}
export default authReducer
login Action과 logout Action을 정의해주고 각 Action의 알맞은 Type 작성해준다.
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export function login(token, memberType) {
return {
type: LOGIN,
payload: {
token,
memberType
}
}
}
export function logout() {
return {
type: LOGOUT
}
}
Redux에서 Store에는 하나의 Reducer만 연결 가능하기 때문에 combineReducers 함수를 이용해서 rootReducer에 Reducer를 통합해주었다.
지금은 하나의 Reducer만 존재하지만Reducer를 추가로 생성하게되면 rootReducer에 추가해주면된다.
const rootReducer = combineReducers({
auth: authReducer
})
export default rootReducer
Redux Store를 생성하고 초기 상태를 rootReducer의 반환 값으로 설정해준다.
store.subscribe(()=>{}) 이부분은 store.subscribe 메서드를 사용하여 Store의 상태 변경을 구독해준다.
Store의 상태 변경이 제대로 이루어지고 있는지 확인하기위해 잠시 넣어주었다.
const store = createStore(rootReducer);
store.subscribe(() => {
console.log('Updated state: ', store.getState());
})
export default store;
생성된 Redux Store를 전역으로 제공하기 위해 애플리케이션의 최상위 Component에서 Provider Component를 활용한다.
이를 통해 하위 Component들이 Redux 상태에 쉽게 액세스 할 수 있다.
// index.js
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>
);
useDispatch 훅을 사용하여 Dispatch 함수를 가져오고,
이를 통해 적절한 Redux Action을 Dispatch 해주면 된다.
아래 코드의 경우 로그인 기능이므로 token 값과 memberType 값을 반환하여 login Action을 Dispatch 해주었다.
/// Login
const dispatch = useDispatch();
const handleLogIn = async () => {
const formData = {
username: userName,
password: password,
login_type: memberType,
}
try {
/// ...
const token = response.data.token;
/// ...
dispatch(login(token, memberType));
}
navigate(-1);
}
catch (error) {
/...
}
}
로그아웃 또한 logout Action을 Dispatch 해주었다.
/// Logout
const dispatch = useDispatch();
const handleLogOut = async () => {
try {
const response = await axios.post(`/accounts/logout/`);
console.log(response);
removeCookie('token', { path: "/"});
dispatch(logout());
alert(response.data.detail);
navigate('/');
window.location.reload();
} catch (error) {
console.error('로그아웃 오류: ', error.response.data);
}
}
이제 이번 글의 핵심인 로그인 유지를 구현할 차례이다.
이전 글인 'JWT 기반 인증 로그인 구현하기'에서 token을 Cookie에 저장하는 것까지 확인 할 수 있다.
로그인 유지는 Cookie에 저장된 token의 유무를 확인하여 처리된다.
만약 token이 존재한다면, getCookie 함수를 이용해 token 값과 memberType값 을 가져온 후 login Action을 Dispatch 해주면 된다.
이러한 과정은 useEffect를 사용하여 Dispatch 값이 변경될 때마다 실행되도록 설정해준다.
위에 로그아웃 코드를 보면
로그아웃 시 removeCookie 함수를 이용해 Cookie에 저장된 token 값을 지워준다.
그래서 한번 로그인 한 상태에서는 로그아웃을 할 때까지 Cookie에 token 값이 존재하기 때문에
페이지를 새로 고침하거나 다른 페이지로 이동하더라도 로그인 상태가 유지된다.
/// App.js
const dispatch = useDispatch();
useEffect(() => {
const token = getCookie('token');
const memberType = getCookie('memberType');
if(token)
dispatch(login(token, memberType));
}, [dispatch])