[SeSACx코딩온] rootReducer를 통한 Redux 상태 관리 개선
Redux 루트 리듀서를 통한 상태 관리 개선
1. 기존 방식의 한계
- 하나의 리듀서만을 사용하여 여러 상태를 관리하는 방식은 애플리케이션이 커지면서 유지보수가 어려워질 수 있습니다.
- rootReducer 없이 각각의 리듀서를 하나의 리듀서로 통합하지 않으면 코드가 매우 복잡해지고 유지보수가 어려워집니다.
1) 기존 방식 코드 (여러 리듀서가 있지만 rootReducer를 사용하지 않은 경우)
// counterReducer.js
const initialState = { number: 100 };
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case "counter/PLUS":
return { number: state.number + 1 };
case "counter/MINUS":
return { number: state.number - 1 };
default:
return state;
}
};
export default counterReducer;
// isVisibleReducer.js
const initialVisibility = true;
const isVisibleReducer = (state = initialVisibility, action) => {
switch (action.type) {
case "TOGGLE_VISIBILITY":
return !state;
default:
return state;
}
};
export default isVisibleReducer;
// index.js
import { createStore } from "redux";
import counterReducer from "./counterReducer";
import isVisibleReducer from "./isVisibleReducer";
// 두 리듀서를 각각 따로 사용하여 스토어 생성 (복잡하고 비효율적)
const store1 = createStore(counterReducer);
const store2 = createStore(isVisibleReducer);
2) 문제점
- 스토어가 여러 개 필요
- 각 리듀서마다 별도로 스토어를 만들어야 하므로 상태 관리가 분산되고 복잡해집니다.
- 상태 접근이 어려움
- 서로 다른 스토어에 있는 상태를 한 곳에서 관리하거나 업데이트하기 어렵습니다.
- 상태 변경의 흐름이 분산
- 여러 리듀서를 관리하다 보면 코드가 분산되어 상태 변경의 흐름을 추적하기 힘듭니다.
2. 루트 리듀서를 통한 상태 관리 개선
1) 루트 리듀서의 개념
- 루트 리듀서(rootReducer)는 여러 개의 리듀서를 결합하여 각 리듀서가 독립적으로 상태를 관리할 수 있도록 해줍니다. 이를 통해 상태 관리를 모듈화하고, 애플리케이션이 확장되어도 각 리듀서가 독립적으로 관리될 수 있습니다.
- 기존의 단일 리듀서 대신
combineReducers
함수를 사용해 여러 리듀서를 결합하는 방식을 사용할 수 있습니다. - combineReducers는 Redux 라이브러리에서 제공하는 공식 함수로, 여러 리듀서를 결합해 하나의 중앙 스토어에서 상태를 관리할 수 있게 해줍니다.
2) Redux 파일 트리 구조
src/
│
├── store/ # Redux 관련 폴더
│ ├── counterActions.js # counter 액션 타입 및 액션 생성자 정의
│ ├── counterReducer.js # counter 상태를 관리하는 리듀서
│ ├── isVisibleReducer.js # isVisible 상태를 관리하는 리듀서
│ └── index.js # 여러 리듀서를 결합한 루트 리듀서 생성
│
├── styles/ # CSS 파일 폴더
│ └── Box.css # Box 컴포넌트 스타일링
│
├── index.js # ReactDOM 및 Redux Provider 설정
└── App3.js # 메인 컴포넌트 (Box 1 ~ 4, 상태 변경)
3) 루트 리듀서 적용 후 코드
// store/index.js
import { combineReducers } from "redux";
import counterReducer from "./counterReducer";
import isVisibleReducer from "./isVisibleReducer";
// 여러 리듀서를 결합하여 rootReducer 생성
const rootReducer = combineReducers({
counter: counterReducer, // counter 상태를 관리하는 리듀서
isVisible: isVisibleReducer // isVisible 상태를 관리하는 리듀서
});
export default rootReducer;
3. Redux 상태 읽기 및 상태 변경
useSelector
와 useDispatch
훅을 사용하여 여러 상태를 읽고, 여러 액션을 디스패치합니다.
import { useSelector, useDispatch } from 'react-redux';
import './styles/Box.css';
import { minus, plus } from './store/counterReducer';
import { changeVisibility } from './store/isVisibleReducer';
function App3() {
const number = useSelector((state) => state.counter.number); // counter 상태 읽기
const isVisible = useSelector((state) => state.isVisible); // isVisible 상태 읽기
const dispatch = useDispatch(); // 액션을 디스패치할 함수 가져오기
return (
<div className="App">
<h1>Redux 리듀서를 통한 상태 관리</h1>
<h2>number: {number}</h2>
<h2>isVisible 값은 {isVisible ? '참' : '거짓'} 입니다.</h2>
{/* 상태 변경 액션 디스패치 */}
<button onClick={() => dispatch(plus())}>PLUS</button>
<button onClick={() => dispatch(minus())}>MINUS</button>
<button onClick={() => dispatch(changeVisibility())}>CHANGE</button>
</div>
);
}
export default App3;
4. 관리 방식의 장점
1) 상태 관리의 확장성
루트 리듀서를 사용함으로써 상태 관리를 모듈화할 수 있습니다. 새로운 상태가 필요할 때마다 기존 리듀서를 수정할 필요 없이, 새로운 리듀서를 만들어 결합하면 됩니다. 이는 애플리케이션이 확장될수록 유지보수가 쉬워지며, 각 상태가 독립적으로 관리되기 때문에 코드의 복잡성이 줄어듭니다.
2) 코드의 가독성과 유지보수성 향상
각 리듀서가 자신이 관리하는 상태에 대해 명확하게 정의되어 있기 때문에, 코드가 직관적이고 가독성이 높아집니다. 여러 상태를 하나의 리듀서에서 관리하던 방식보다 훨씬 유지보수가 용이하며, 상태 변화의 흐름을 쉽게 파악할 수 있습니다.
[ Redux 루트 리듀서 구조 시각화 ]
Redux Store
│
├── Actions (액션)
│ ├── Action Type (액션 타입)
│ └── Action Creator (액션 생성자)
│ ├── plus() --> { type: 'counter/PLUS' }
│ └── changeVisibility() --> { type: 'isVisible/CHANGE' }
│
├── Reducers (리듀서)
│ ├── counterReducer() --> counter 상태 변경 함수
│ ├── 'counter/PLUS' --> state.number + 1
│ └── 'counter/MINUS' --> state.number - 1
│ └── isVisibleReducer() --> isVisible 상태 변경 함수
│ └── 'isVisible/CHANGE' --> state.toggle
│
└── Store (스토어)
├── 초기 상태 (initialState)
│ ├── counter: { number: 100 }
│ └── isVisible: true
├── 루트 리듀서 (rootReducer)
├── 여러 상태를 결합하여 중앙에서 관리
└── 컴포넌트가 useSelector, useDispatch로 상태 읽기/변경