팀 프로젝트를 하면서 로그인 정보를 유지할 방법에 대해 고민을 해봤다.
React는 새로고침하면 처음부터 다시 렌더링 되기 때문에 로그인 상태에서 UX가 나빴기 때문이다.
팀에서는 로그인 관련 정보를 redux store에 저장하고 사용 중이었는데,
새로고침하면 이 또한 사라져버리기 때문에 이를 유지할 방법을 찾아야했다.
어떻게 하면 이 녀석을 영속적으로 만들 수 있을까?
이를 해결해주는 라이브러리인redux-persist
를 알아보자!
redux-persist
는 어떻게 redux store를 영속적으로 만들어주는걸까?
필자가 대략 이해한 바로는 다음과 같다.
redux-persist
를 통해 특정 위치에 store 값이 저장된다.이러한 흐름을 통해서 redux store의 증발을 막는 것이다.
아무래도 redux
와 관련이 깊은 라이브러리이다보니, 사용법도 매우 유사하다.
우선, 사용하기 전 코드부터 살펴보자.
redux
만 사용하고 있을 때는 보통 다음과 같은 코드 구조를 가진다.
// src/index.tsx
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store/configureStore';
import './styles/index.module.scss';
import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Provider store={store}>
<App />
</Provider>,
);
// src/reducers/index.ts
import { combineReducers } from 'redux';
import modalSlice from './modal';
import menuSlice from './slideMenu';
import myPostSlice from './myPost';
import myReservationSlice from './myReservation';
import chatSlice from './chat';
import userSlice from './user';
import accessSlice from './access';
import reservationSlice from './reservation';
import CheckModeSlice from './checkMode';
const rootReducer = combineReducers({
modal: modalSlice.reducer,
menu: menuSlice.reducer,
myPost: myPostSlice.reducer,
myReservation: myReservationSlice.reducer,
chat: chatSlice.reducer,
user: userSlice.reducer,
access: accessSlice.reducer,
reservation: reservationSlice.reducer,
checkMode: CheckModeSlice.reducer,
});
export default rootReducer;
// src/store/configureStore.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from 'reducers';
export const store = configureStore({
reducer: rootReducer,
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
redux-persist
를 적용하는 것은 어렵지 않다.
// src/index.tsx
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store/configureStore';
import './styles/index.module.scss';
import App from './App';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
export let persistor = persistStore(store);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
);
Provider
를 적용한 곳에 PersistGate
를 만들어주고
Provider
에 store
속성값을 넣는 것 처럼,
persistStore
를 만들어 PersistGate
의 persistor
속성값으로 넣어준다.
// src/store/configureStore.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from 'reducers';
import storageSession from 'redux-persist/lib/storage/session';
import { persistReducer } from 'redux-persist';
const persistConfig = {
key: 'root', // 브라우저 Storage에 저장될 이름(key)을 지정
storage: storageSession, // 어떤 브라우저 Storage를 사용할지 지정
whitelist: ['user'], // 어떤 reducer를 영속적으로 만들지 지정
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({ serializableCheck: false }),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
configureStore
를 생성했던 곳에서는 redux-persist
의 기본 설정을 진행한다.
우선, persistConfig
부터 살펴보자.
import storageSession from 'redux-persist/lib/storage/session';
const persistConfig = {
key: 'root', // 브라우저 Storage에 저장될 이름(key)을 지정
storage: storageSession, // 어떤 브라우저 Storage를 사용할지 지정
whitelist: ['user'], // 어떤 reducer를 영속적으로 만들지 지정
};
필자는 sessionStorage
에 저장하고 싶었기 때문에, storageSession
을 적용했다.
만약, localStorage
에 저장하고 싶다면 다른 것을 import해서 사용하면 된다.
whitelist
에는 어떤 reducer를 영속적으로 만들지 지정해줄 수 있다.
반대로, blacklist
에는 어떤 reducer를 redux-persist
에서 배제시킬지 지정할 수 있다.
이후 rootReducer
와 persistConfig
를 합쳐서 새로운 루트 리듀서를 만들어준다.
여기까지는 이해가 잘 되는데, configureStore
를 생성하는 부분에 생소한 것이 있다.
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({ serializableCheck: false }),
});
middleware
?? 갑자기 얘가 왜 튀어나온걸까?
이 설정을 하지 않으면, 아래와 같은 에러가 발생하기 때문이다.
따라서 middleware
에 해당 설정을 넣어 해결해주도록하자.
빌드 과정에서는 문제가 없는데, 브라우저 상에서 에러가 보이기 때문에 잡아주는게 좋을 것 같다.
이와 관련된 이슈도 redux-persist github에 생성되어 있으며,
redux-toolkit docs에서도 redux-persist
사용법에 대해 같은 내용을 안내하고 있다.
본인이 redux-persist
의 어떤 기능을 사용하는지에 따라 커스텀도 가능한 것으로 보인다.
이제 코드를 작성했으니, 어떻게 작동하는건지 확인해보자.
원래대로라면 로그인 후, 새로고침을 하면 로그인 정보가 날아가야한다.
하지만 redux-persist
를 적용했기 때문에 sessionStorage
에 로그인 정보가 저장될 것이다.
로그인이 유지되는 것은 확인된다!
그러면 sessionStorage
를 확인해보자.
persistConfig
에 key
라는 값으로 설정했던 root
라는 이름이 보인다.
그리고 value
에는 user
리듀서에 존재했던 값들이 보이고 있다.
redux-persist
가 브라우저 Storage에 값을 저장하고 영속성을 보장하는 모습을 확인할 수 있다!
redux-persist
는 purge()
메서드를 제공한다.
영속적 store에 저장된 것을 초기화하는 기능인데, 헷갈리면 안되는 것이 있다.
purge()
메서드를 사용한다고 해서 redux store의 값이 변경되는 것이 아니다.
예를들어, 로그아웃 기능을 만들 때 persistor.purge()
를 사용한다고 해서,
로그인 정보를 담고 있던 redux store가 초기화되지 않는다는 것이다.
따라서, sessionStorage
에도 변경점은 없다.
purge()
를 사용한다면 이 점을 주의해서 사용해야할 것 같다.
어떤 action을 통해 store를 변경해야한다면 상태를 변경하는 부분은 평소처럼 따로 작성해줘야한다.
purge()
는 redux store에 변경을 일으키지않는다.
redux-persist npm docs
redux-toolkit docs
redux-persist github
choyeon-dev님 블로그
sj0826님 블로그
stackoverflow 질문글
_jouz_ryul님 블로그