[React] redux-persist

박기영·2023년 6월 18일
0

React

목록 보기
30/32

팀 프로젝트를 하면서 로그인 정보를 유지할 방법에 대해 고민을 해봤다.
React는 새로고침하면 처음부터 다시 렌더링 되기 때문에 로그인 상태에서 UX가 나빴기 때문이다.
팀에서는 로그인 관련 정보를 redux store에 저장하고 사용 중이었는데,
새로고침하면 이 또한 사라져버리기 때문에 이를 유지할 방법을 찾아야했다.
어떻게 하면 이 녀석을 영속적으로 만들 수 있을까?
이를 해결해주는 라이브러리인 redux-persist를 알아보자!

원리

redux-persist는 어떻게 redux store를 영속적으로 만들어주는걸까?
필자가 대략 이해한 바로는 다음과 같다.

  1. 브라우저에서 활동하면서 redux store에 값이 저장된다.
  2. 새로고침을 누른다.
  3. 미리 설정해둔 redux-persist를 통해 특정 위치에 store 값이 저장된다.
  4. 새로고침이 끝나고 처음부터 다시 렌더링이 된다.
  5. 이 때, 특정 위치에 저장해놨던 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를 만들어주고
Providerstore 속성값을 넣는 것 처럼,
persistStore를 만들어 PersistGatepersistor 속성값으로 넣어준다.

// 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에서 배제시킬지 지정할 수 있다.

이후 rootReducerpersistConfig를 합쳐서 새로운 루트 리듀서를 만들어준다.

non-serializable 에러 발생!

여기까지는 이해가 잘 되는데, 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를 확인해보자.

참고 이미지

persistConfigkey라는 값으로 설정했던 root라는 이름이 보인다.
그리고 value에는 user 리듀서에 존재했던 값들이 보이고 있다.
redux-persist가 브라우저 Storage에 값을 저장하고 영속성을 보장하는 모습을 확인할 수 있다!

purge()에 대하여

redux-persistpurge() 메서드를 제공한다.
영속적 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님 블로그

profile
나를 믿는 사람들을, 실망시키지 않도록

0개의 댓글