현재 회사에서 redux를 새로 도입하는 과정에서 미리 공부하는 중에 구글링해서 찾아낸 글들과 챗지피티의 도움을 받으며 해보던 중에 그 과정을 정리하고 발생했던 에러와 해결 방법을 정리해보았다.
redux-persist란?
redux의 상태는 일시적이기 때문에, 브라우저를 새로고침하면 모든 상태값들이 초기화된다.
사용자가 새로고침할 때마다 값이 사라져버리면 상태 관리를 하는 의미가 없다.
이를 개선하기 위해, redux-persist라는 라이브러리를 사용하여 redux의 상태를 브라우저 내에 저장할 수 있다.
이 라이브러리는 redux store 값들을 영구 저장소에 저장하는 데 사용되는 라이브러리로,
브라우저를 새로고침하더라도 상태를 유지할 수 있게 된다.
현재 코드 상태
지금은 특별한 프로젝트가 아니라, redux 도입 과정에서 튜토리얼로 진행한 카운터로 작업 중이다.
그리고 redux-devtools를 사용하고 있는 중이다.
카운터 관련하여 redux를 다루고 있는 파일은 크게 4개의 파일이 있다.
1) pages/_app.ts : 최종적으로 root 리듀서를 적용하는 파일
2) pages/store/action/counter.ts : counter 컴포넌트에 대한 액션 타입 및 생성자 다루는 파일
3) pages/store/reducer/counter.ts : counter 컴포넌트에 대한 리듀서 파일
4) pages/store/root-reducer.ts : 여러 리듀서들을 한 번에 관리하는 파일
여기서 couter 리듀서 자체를 다루는 파일은 손 댈 필요가 없고, 나머지 2개의 파일(root-reducer, _app)을 수정해주면 된다.
적용 과정
1. redux-persist 설치하기
npm install redux-persist
2. redux-persist의 PersistGate 컴포넌트를 App 컴포넌트 내부에 추가하기
next에서 App 컴포넌트는 pages/_app.ts 파일 내에 존재한다.
적용 전 코드는 아래와 같다.
// _app.tsx
import type { AppProps } from "next/app";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
import logger from "redux-logger";
import thunk from "redux-thunk";
import "../styles/globals.css";
import rootReducer from "../store/root-reducer"
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(logger, thunk))
);
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
PersistGate 컴포넌트는 지속적인 상태를 검색해서 리덕스에 저장될 때까지 UI의 렌더링을 지연시켜주는 역할을 한다.
이 PersistGate 컴포넌트로 App 컴포넌트를 감싸주면 된다.
// _app.tsx
import type { AppProps } from "next/app";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
import logger from "redux-logger";
import thunk from "redux-thunk";
import "../styles/globals.css";
import rootReducer from "../store/root-reducer"
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(logger, thunk))
);
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<PersistGate loading={} persistor={} >
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
이 때 PersistGate 컴포넌트에 넘겨줘야 하는 props는 2가지가 있다.
1. loading : 초기화 과정에서, 그러니까 리덕스에 저장하는 과정에서 보여줄 컴포넌트를 지정할 수 있다. null 또는 undefined로 지정해둔다면 아무것도 렌더링하지 않는다.
2. persistor : redux store의 상태를 지속성있게 유지하기 위한 메소드와 설정을 가지고 있는 persistor 객체를 넘겨주어 redux store의 상태를 로딩하는데 사용한다.
일단은 loading에는 null값을 주어 아무것도 렌더링하지 않게 했고, (튜토리얼이니까)
이제 persistor를 생성해보자
3. persistor와 store 생성하기
이 부분이 약간 구조적으로 조금 헤맸던 부분이었다.
현재 root-reducer 파일의 코드는 아래와 같다.
// root-reducer.ts
import { combineReducers, createStore } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import counter from "./reducer/counter";
import user from "./reducer/user";
const rootReducer = combineReducers({
counter,
user,
});
export default rootReducer;
persister를 생성하기 위해서, persistReducer 함수를 써야 한다. 이 함수는 기존 리듀서의 역할에 더해서 액션이 발생할 때마다 localStorage에 상태를 저장하는 추가적인 로직이 포함되어 있다. 이를 통해 새로고침을 해도 어플리케이션의 상태를 유지할 수 있다.
persistReducer 함수는 인자로 persistConfig를 받는다. 이 persistConfig 객체는 저장소의 설정을 구성한다.
1) localStorage의 key 이름 2) 저장할 데이터의 버전 3) 저장 방식 등을 지정할 수 있다.
나는 root라는 key 이름으로 persistConfig를 설정해주었다. 그리고 이 config와 기존 리듀서를 persistReducer 함수 안에 넣어준 후, 이 리듀서를 export 해주면 된다.
// root-reducer.ts
import { combineReducers, createStore } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import counter from "./reducer/counter";
import user from "./reducer/user";
const persistConfig = {
key: "root",
storage,
version: 1,
};
const rootReducer = combineReducers({
counter,
user,
});
export const persistedReducer = persistReducer(persistConfig, rootReducer);
4. App 컴포넌트에 persister 넣어주기
3번에서 만든 persistedReducer를 _app.tsx로 가져와서, 2번에서 만든 PersistGate 컴포넌트의 props로 넣어주면 된다.
먼저, store를 만드는 부분(createStore)에서 3번에서 만든 persistedReducer로 교체해서 Provider 컴포넌트가 props로 받고 있는 store를 교체해준다.
이후, persistStore()함수를 이용해서 해당 store를 지속성 있는 저장소로 만든 후, 이를 PersistGate의 props로 넘겨준다.
persistStore() 함수를 사용하면 Redux store의 상태가 유지된다.
// _app.tsx
import type { AppProps } from "next/app";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
import logger from "redux-logger";
import thunk from "redux-thunk";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";
import "../styles/globals.css";
import { persistedReducer } from "../store/root-reducer";
const store = createStore(
persistedReducer, // rootReducer -> persistedReducer로 교체
composeWithDevTools(applyMiddleware(logger, thunk))
);
const persistor = persistStore(store); // 지속성 있는 저장소로 만듦
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
적용 완료한 코드
// root-reducer.ts
import { combineReducers, createStore } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import counter from "./reducer/counter";
import user from "./reducer/user";
const persistConfig = {
key: "root",
storage,
version: 1,
};
const rootReducer = combineReducers({
counter,
user,
});
export const persistedReducer = persistReducer(persistConfig, rootReducer);
// _app.tsx
import type { AppProps } from "next/app";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
import logger from "redux-logger";
import thunk from "redux-thunk";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";
import "../styles/globals.css";
import { persistedReducer } from "../store/root-reducer";
const store = createStore(
persistedReducer,
composeWithDevTools(applyMiddleware(logger, thunk))
);
const persistor = persistStore(store);
export default function App({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
);
}
이런 식으로 localStorage에 redux값이 적용되고, 새로고침해도 상태가 유지되는 것을 확인할 수 있다.
참고 자료
1. https://www.npmjs.com/package/redux-persist
redux-persist
persist and rehydrate redux stores. Latest version: 6.0.0, last published: 4 years ago. Start using redux-persist in your project by running `npm i redux-persist`. There are 1094 other projects in the npm registry using redux-persist.
www.npmjs.com
'Library-Framework > Redux' 카테고리의 다른 글
[Redux] Redux 사용해서 Counter 구현하기 (Next.js + TypeScript) (0) | 2023.05.08 |
---|---|
Next.js + TypeScript + Redux 시작하기 (+ redux-devtools 사용) (0) | 2023.05.07 |