리덕스 예제를 보면 숫자 카운트 하는 거라던지 to do리스트뿐이었다....
혹시나 Redux와 redux-persist, redux toolkit을 같이 사용하고 싶다거나
처음 도입하는 사람이 이 글을 본다면,, 도움이 됐으면 좋겠어서 글을 남겨본다!😁✌️

👽 개요

  • 프로젝트를 진행하면서 10여 개 이상의 페이지에 값을 전달해줘야 하는 로직이 필요했다. 기존에는 Navigate를 이용하여 값을 전달해 줬는데 많은 프로퍼티를 각 페이지별 값을 받아오는 코드가 불필요하게 중복적으로 구현되어 있었다. 값이 1개 2개 정도로 작은 경우에는 효율적이었겠지만,,,, 이렇게 많은 state와 많은 페이지로 이동을 시켜줘야 한다면 유지보수마저 끔찍했을 것이다.
  • 위에 사진처럼 해당하는 코드가 각 페이지에 필요했던 구조였다.
  • 값전달 -> 값 받기 또 다음페이지로 이동할 때, 값전달 -> 값 받기 x10개 이상..... 계속 반복되는 구조
  • 상태관리는 useState, useEffect, recoil, context API 만 사용해 봐서 다양한 상태관리 라이브러리가 있다는 걸 알았지만! redux는 세팅하는 게 꽤 난이도가 있다고 들어서 공부해 볼 겸 리덕스를 프로젝트에 도입하게 되었다.

👽 redux란?

 

기존 상태관리/Redux

  • 리덕스는 상태관리 라이브러리 중 하나이다. 큰 리액트 프로젝트에서 컴포넌트끼리 state 내려주기를 반복하다 보면 Props driling을 야기할 수 있다.
  • 리덕스 툴킷을 사용할 경우 기존 리덕스 문법보다 훨씬 쉬운 방법으로 상태관리가 가능하다. 리덕스 개발사 쪽에서도 리덕스 툴킷 사용을 권장한다.
  • 리덕스 라이브러리를 이용하여 상태관리를 할 경우, store는 새로고침을 할 경우 state가 사라진다. redux-persist를 사용하면 새로고침을 하더라도 localStroage/sessionStroage 등에 저장이 가능하다.

👽 redux 기본용어

  • store : 컴포넌트의 상태를 관리하는 저장소. 하나의 프로젝트는 하나의 스토어만 가짐.
  • action : 스토어의 상태를 변경하기 위한 객체.
  • reducer : 현재 상태와 액션 객체를 받아 새로운 상태를 리턴하는 함수.
  • dispatch : store의 내장 함수. 액션 객체를 넘겨줘 상태를 업데이트시켜 주는 역할.
  • subscribe : store의 내장 함수. reducer가 호출될 때 서브스크라이브된 함수 및 객체를 호출.

자 그럼 이제 리덕스와 기본용어를 알았으니 사용하러 가보자.

1. 설치

npm install redux
npm install @reduxjs/toolkit
npm install redux-persist

나는 참고로 아래와 같은 환경에서 구현했다.

"@reduxjs/toolkit": "^1.9.2",
"redux": "^4.2.0",
"redux-actions": "2.6.5",
"redux-devtools-extension": "2.13.0",
"redux-logger": "3.0.6",
"redux-persist": "^6.0.0",

 

2. src에 redux폴더를 만든 뒤 store.js를 만든다. (위치는 상관없음)

import { configureStore } from '@reduxjs/toolkit';
import storage from 'redux-persist/lib/storage';
import { combineReducers } from '@reduxjs/toolkit';
import { persistReducer, PERSIST } from 'redux-persist';
import testSlice from './testSlice';

const reducers = combineReducers({
  test: testSlice.reducer,
});

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['testFilter'],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [PERSIST],
      },
    }),
});

export default store;
  • configureStore : store생성 및 Redux DevTools 확장시켜 준다. 기존 리덕스에서는 미들웨어가 한 개 이상이면, 여러 메서드를 통해 매우 코드가 길어졌다. configureStore를 통해 별도의 메서드 없이 바로 미들웨어를 추가할 수 있다.
  • key(필수) : 스토리지에서 사용되는 키 명칭
  • storage(필수) - 만약에 sessionStorage에 저장하고 싶다면 import 해주고 storage: storageSession를 해주면 된다.
import storageSession from 'redux-persist/lib/storage/session';
  • whitelist : 스토리지를 통해 관리할 항목
  • persistReducer를 통해 persist를 적용하고, 적용한 리듀서를 스토어로 만들어서 내보낸다.

 

3. index.js에 스토어를 연동할 컴포넌트를 Provider로 감싸주고 props로 사용할 스토어를 지정해 준다.

  • Provider는 react-redux에서 리액트 앱에 스토어를 연동할 수 있게 해주는 컴포넌트이다.
  • 추가적으로 redux-persist 또한 사용할 것이기 때문에 PersistGate에 persistor로 props로 넘겨준다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import store from './store/srore';

const persistor = persistStore(store);
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  // <React.StrictMode>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  // </React.StrictMode>
);

reportWebVitals();

4. createSlice를 생성해 준다.

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  test1: null,
};

export const testFilter = createSlice({
  name: 'testFilter',
  initialState,
  reducers: {
    setTestFilter: (state, action) => {
      state.test1 = action.payload.test1;
    },
    initTestFilter: (state) => {
      state.test1 = initialState.test1;
    },
  },
});

export const { setTestFilter, initTestFilter } =
  testFilter.actions;

export const test1 = (state) => state.test1;

export default testFilter;
  • 기존 리덕스에서는 액션을 디스패치하기 위한 별도의 함수가 필요했고, 액션의 객체를 리듀서를 통해 리턴하는 구조였다. createSlice()는 액션에 대한 함수 설정과 리듀서를 따로 생성하지 않아도 된다.
  • initialState를 통해 state의 처음 상태를 정의한다.
  • reducers에서 액션을 설정한다.
  • setTestFilter와 initTestFilter를 export 해서 App.jsx에 import 한다.
  • slice는 testSlice.reducer로 내보낸다. store.js는 위 파일을 전부 리듀서로 받는다.

 

  • initialState : 초기값 
  • name : 리듀서 이름 
  • reducers : 상태가 변하면 어떻게 실행될지 정하는 부분. 
  • payload : 바꾸고 싶은 데이터를 원하는 곳에 넘겨주는 역할.

5. useSelector, useDispatch로 상태 접근하기

  • useSelector()는 기존 리덕스의 connect()를 이용하지 않고 리덕스의 상태를 조회할 수 있다.
  • useDispatch()는 생성한 액션을 발생시키며, 액션생성 함수를 가져온다.
// App.jsx

import React from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { setTestFilter, initTestFilter } from './testSlice';

export default function App() {
  const testState = useSelector(state => state.testFilter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={() => dispatch(setTestFilter())}>값추가</button>
      Value: { testState } 
      <button onClick={() => dispatch(initTestFilter())}>초기화</button>
    </div>
  );
}
  • useSelector()로 스토어에서 현재 상태 값을 가져온다.
  • useDispatch()를 통해 변경되는 값을 스토어로 전달한다.

🔥🔥🔥 그리고 꼭 주의해야 할 사항! 🔥🔥🔥

  • 위의 코드에서 officeSheet는 리덕스 store 초기값 전체를 지칭한다.
  • 리덕스는 순수함수이다. 이전 상태는 절대로 건드리지 않고, 변화를 일으킨 새로운 상태를 객체로 만들어서 반환해야 한다.
  • 리덕스에서는 다른 데이터를 덧씌우고 싶을 경우 꼭 깊은 복사로 앞 전의 데이터를 복사해 주기..!! 아니면 앞에 저장한 값이 날아간다! ⭐⭐⭐

 

⚠️ 만났던 오류

[에러] TypeError: Cannot read property 'usersState' of undefined

- useSelector를 통해 createSlice에서 선언한 초기값을 가져오려고 했지만 해당하는 오류가 떴다.

- store에 선언했지만 계속 해당하는 오류가 떴는데 알고 보니 name 명이 달라서 생긴 오류였다.

- 요소를 제대로 불러오지 못할 경우 store에 선언된 이름들을 제대로 확인해 보자...!!! 역시 오류가 났을 때는 오타가 있는지 먼저 확인해 보자..

 

혹시나 궁금하거나 이해하기 어려운 부분 댓글로 적어주면 상세히 설명해 주겠다! 읽어줘서 감사합니다🤴🏻

하단은 구현하면서 정말 도움이 많이 되었던 글이다!

 

ref.

https://velog.io/@gunu/리덕스에-스토리지-적용하기

https://velog.io/@sweet_pumpkin/무작정따라하기-최고-리덕스야-고맙다-Redux-Redux-Toolkit-알아보기

https://velog.io/@mael1657/Redux-toolkit으로-상태관리하기

https://kyounghwan01.github.io/blog/React/redux/redux-persist/#%E1%84%89%E1%85%A9%E1%84%80%E1%85%A2-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%B2