🔁
EPISODE 04
action · dispatch · reducer · 불변성
useReducer
복잡한 상태 로직을 useState 대신 useReducer로 분리하기 — action/dispatch/reducer 패턴, useState와의 선택 기준, 그리고 불변성을 유지한 상태 업데이트.
ReactuseReducer불변성
소요 시간
⏱ 45~60분
난이도
📊 중급
선수 조건
🎯 react-03
결과물
여러 상태가 얽힌 cart 같은 복잡 로직을 reducer로 분리
이 강의에서 배우는 것
- 1action/dispatch/reducer 3요소를 안다
- 2switch문으로 reducer를 작성한다
- 3useState와 useReducer의 선택 기준을 안다
- 4스프레드/map/filter로 불변성을 유지한다
1. 문법
jsx
const [state, dispatch] = useReducer(reducer, initialState);| 요소 | 역할 |
|---|---|
| state | 현재 상태 |
| dispatch | action을 보내는 함수 |
| reducer | (state, action) => newState 순수 함수 |
| initialState | 초기 상태 |
2. action / dispatch / reducer
jsx
const ADD_ITEM = 'ADD_ITEM';
const REMOVE_ITEM = 'REMOVE_ITEM';
const CLEAR_CART = 'CLEAR_CART';
function cartReducer(state, action) {
switch (action.type) {
case ADD_ITEM:
return { ...state, items: [...state.items, action.payload] };
case REMOVE_ITEM:
return { ...state, items: state.items.filter(i => i.id !== action.payload) };
case CLEAR_CART:
return { ...state, items: [] };
default:
return state;
}
}
function Cart() {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => dispatch({ type: ADD_ITEM, payload: product });
const removeItem = (id) => dispatch({ type: REMOVE_ITEM, payload: id });
}3. useState vs useReducer
| 상황 | 권장 |
|---|---|
| 단순한 값 하나 | useState |
| 여러 state가 서로 연관 | useReducer |
| 다음 상태가 이전 상태에 의존 | useReducer |
| 상태 업데이트 로직이 복잡 | useReducer |
| 테스트가 중요 (reducer는 순수) | useReducer |
jsx
// useState로 분산되면 복잡
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [discountRate, setDiscountRate] = useState(0);
const [appliedCoupon, setAppliedCoupon] = useState(null);
// useReducer로 묶음
const [cartState, dispatch] = useReducer(cartReducer, {
items: [], total: 0, discountRate: 0, appliedCoupon: null,
});4. 불변성 유지
jsx
// ❌ 직접 변경 — 리렌더링 안 됨!
case ADD_ITEM:
state.items.push(action.payload);
return state;
// ✅ 새 배열 생성
case ADD_ITEM:
return { ...state, items: [...state.items, action.payload] };
// ✅ 특정 항목 수정 — map으로 새 배열
case UPDATE_QUANTITY:
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: action.payload.quantity }
: item
),
};
// ✅ 특정 항목 제거 — filter
case REMOVE_ITEM:
return { ...state, items: state.items.filter(i => i.id !== action.payload) };