combineReducers는 Redux에서 사용되는 여러 개의 리듀서(reducer)를 하나로 결합하는 함수입니다.
Redux 애플리케이션에서는 하나 이상의 리듀서를 사용하여 각각의 상태를 관리하게 됩니다. combineReducers 함수는 이러한 리듀서들을 하나의 상위 리듀서로 결합하고, 각각의 리듀서가 관리하는 상태들을 통합하여 전체 상태를 관리하는 역할을 합니다.
import { createStore, applyMiddleware, combineReducers, Store } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
// RootState 애플리케이션의 전체 상태 구조를 정의
// ❗interface 는 객체의 구조를 정의하는 데 사용
interface RootState {
counter: number;
user: string | null;
}
// 액션 타입 정의
// ❗enum 열거형은 연관된 상수 값을 그룹화
enum ActionType {
INCREASE = 'INCREASE',
DECREASE = 'DECREASE',
SET_USER = 'SET_USER',
}
// 액션 생성 함수 정의
interface IncreaseAction {
type: ActionType.INCREASE;
}
interface DecreaseAction {
type: ActionType.DECREASE;
}
interface SetUserAction {
type: ActionType.SET_USER;
payload: string | null;
}
type Action = IncreaseAction | DecreaseAction | SetUserAction;
// 초기 상태 정의
const initialState: RootState = {
counter: 0,
user: null,
};
// 리듀서 함수 정의 "counter"와 "user" 상태를 업데이트
const counterReducer = (state = initialState.counter, action: Action): number => {
switch (action.type) {
case ActionType.INCREASE:
return state + 1;
case ActionType.DECREASE:
return state - 1;
default:
return state;
}
};
const userReducer = (state = initialState.user, action: Action): string | null => {
switch (action.type) {
case ActionType.SET_USER:
return action.payload;
default:
return state;
}
};
// 루트 리듀서 정의
const rootReducer = combineReducers({
// 키(key) : counter, user
counter: counterReducer,
user: userReducer,
});
// 스토어 타입 정의
export type AppStore = Store<RootState, Action>;
// 미들웨어 적용
const middleware = [thunk];
const enhancers = [applyMiddleware(...middleware)];
// 개발 환경에서 Redux DevTools 확장 기능 사용
if (process.env.NODE_ENV === 'development') {
enhancers.push(composeWithDevTools());
}
// 스토어 생성
const store: AppStore = createStore(rootReducer, initialState, ...enhancers);
export default store;
Redux 액션 타입은
enum으로 정의하는 것을 권장합니다.
payload: 일반적으로 액션에서 전달되는 추가적인 데이터를 의미합니다.
SetUserAction에서 payload가 string | null 타입으로 정의된 이유는 사용자 정보를 나타내는 문자열(string) 또는 사용자가 없음을 나타내는 null 값을 가질 수 있기 때문입니다.
// CounterComponent.tsx
import { useSelector, useDispatch } from 'react-redux';
import { AppStore, IncreaseAction, DecreaseAction } from './store';
export const CounterComponent = () => {
const dispatch = useDispatch();
const counter = useSelector((state: AppStore) => state.counter);
const increaseCounter = () => {
const action: IncreaseAction = { type: ActionType.INCREASE };
dispatch(action);
};
const decreaseCounter = () => {
const action: DecreaseAction = { type: ActionType.DECREASE };
dispatch(action);
};
return (
<div>
<h2>Counter: {counter}</h2>
<button onClick={increaseCounter}>Increase</button>
<button onClick={decreaseCounter}>Decrease</button>
</div>
);
};
// UserComponent.tsx
import { useSelector, useDispatch } from 'react-redux';
import { AppStore, SetUserAction } from './store';
export const UserComponent = () => {
const dispatch = useDispatch();
const user = useSelector((state: AppStore) => state.user);
const setUser = (username: string) => {
const action: SetUserAction = {
type: ActionType.SET_USER,
payload: username,
};
dispatch(action);
};
return (
<div>
<h2>User: {user}</h2>
<button onClick={() => setUser('John')}>Set User</button>
<button onClick={() => setUser(null)}>Clear User</button>
</div>
);
};
combineReducers 함수를 사용하여 counterReducer와 userReducer를 합친 후, 각각의 리듀서가 관리하는 상태를 counter와 user라는 키(key)로 통합합니다. 이렇게 생성된 rootReducer는 전체 Redux 상태에서 각 리듀서가 담당하는 부분 상태를 관리합니다.
뛰어난 글이네요, 감사합니다.