React 애플리케이션을 개발하다 보면, 상태 관리의 중요성은 아무리 강조해도 지나치지 않습니다. 특히 애플리케이션의 복잡성이 커질수록 효율적인 상태 관리는 개발 경험과 유지보수성에 큰 영향을 미칩니다. 'useState' 훅이 간단한 상태 관리에 매우 유용하지만, 좀 더 복잡하고 예측 가능한 상태 로직을 다룰 때는 'useReducer' 훅이 훨씬 더 체계적인 해결책을 제공합니다. 이 글에서는 'useReducer'가 무엇인지, 왜 필요한지, 그리고 실제 예제를 통해 어떻게 활용할 수 있는지 자세히 알아보겠습니다.
useReducer란 무엇인가요?
'useReducer' 훅은 React에서 리듀서 함수를 통해 상태를 관리하는 데 사용되는 도구입니다. Redux와 같은 외부 라이브러리의 핵심 원칙을 따르지만, React 자체에서 제공하는 훅이기 때문에 별도의 라이브러리 설치 없이 사용할 수 있다는 큰 장점이 있습니다. 'useReducer'를 사용하면 상태 구조와 상태를 업데이트하는 로직을 한 곳에 정의할 수 있어서 복잡한 상태를 더 쉽게 이해하고 유지보수할 수 있습니다.
useReducer의 기본 구조
'useReducer' 훅은 다음과 같은 형태로 사용됩니다:
const [state, dispatch] = useReducer(reducerFunction, initialState);
여기에 사용되는 각 요소들은 다음과 같은 의미를 가집니다:
- state: 리듀서에 의해 관리되는 현재 상태 값입니다.
- dispatch: 상태를 업데이트하기 위한 '액션'을 리듀서 함수로 보내는 데 사용되는 함수입니다. 이 'dispatch' 함수를 호출함으로써 상태 변경을 요청합니다.
- reducerFunction: 현재 상태와 액션 객체를 인수로 받아 새로운 상태를 반환하는 '순수 함수'입니다. '순수 함수'라는 것은 동일한 입력에 대해 항상 동일한 출력을 반환하고, 외부 상태를 변경하지 않는다는 의미입니다.
- initialState: 상태의 초기 값을 정의합니다.
useReducer 사용의 이점
'useReducer'를 사용해야 하는 이유는 여러 가지가 있습니다. 특히 애플리케이션의 규모가 커지거나 상태 로직이 복잡해질수록 그 이점은 더욱 명확해집니다.
- 중앙 집중식 로직: 모든 상태 업데이트 로직이 하나의 리듀서 함수 안에 모여 있습니다. 이는 애플리케이션의 어떤 부분에서든 액션이 발생했을 때 상태가 어떻게 변할지 예측하기 쉽게 만듭니다. 또한, 디버깅을 할 때 상태 변경의 흐름을 한눈에 파악할 수 있어 효율적입니다.
- 복잡한 상태 변경의 더 나은 처리: 여러 개의 관련 데이터 조각을 동시에 다루거나, 이전 상태를 기반으로 복잡한 계산을 통해 새로운 상태를 도출해야 할 때 'useReducer'는 코드를 훨씬 간결하고 명확하게 만들어 줍니다. 'useState'를 여러 번 사용해야 하는 상황에서 'useReducer'는 단일 상태 객체로 이를 통합하여 관리할 수 있게 해줍니다.
- 향상된 가독성 및 유지보수성: 'useReducer'는 상태 정의와 UI 렌더링 로직을 분리하는 데 도움을 줍니다. 컴포넌트 내부의 코드가 상태 업데이트 로직으로 복잡해지는 것을 막고, UI와 관련된 부분에만 집중할 수 있게 하여 컴포넌트를 더 깔끔하고 읽기 쉽게 만듭니다.
실용적인 예제: 쇼핑 카트 관리하기
'useReducer'의 개념을 더 명확하게 이해하기 위해 실제 시나리오를 통해 살펴보겠습니다. 여기서는 'useReducer'를 사용하여 간단한 쇼핑 카트의 항목을 추가하고 제거하는 기능을 구현해 보겠습니다.
1단계: 초기 상태 및 리듀서 함수 정의
먼저, 쇼핑 카트의 초기 상태를 설정하고, 카트 항목을 추가하거나 제거하는 로직을 담을 리듀서 함수를 정의합니다.
const initialState = {
items: [],
totalAmount: 0,
};
function cartReducer(state, action) {
switch(action.type) {
case 'ADD_ITEM':
const updatedItems = [...state.items, action.item];
const updatedTotalAmount = updatedItems.reduce((total, item) => total + item.price * item.quantity, 0);
return { items: updatedItems, totalAmount: updatedTotalAmount };
case 'REMOVE_ITEM':
const filteredItems = state.items.filter(item => item.id !== action.id);
const newTotalAmount = filteredItems.reduce((total, item) => total + item.price * item.quantity, 0);
return { items: filteredItems, totalAmount: newTotalAmount };
default:
return state;
}
}
이 코드에서 initialState는 카트에 담긴 상품들과 총액을 관리하는 객체입니다. cartReducer 함수는 action.type에 따라 ADD_ITEM 또는 REMOVE_ITEM 로직을 수행하여 새로운 상태를 반환합니다. 이 리듀서는 현재 상태와 액션을 기반으로 완전히 새로운 상태 객체를 생성하여 반환하는 '순수 함수'의 원칙을 잘 따르고 있습니다.
2단계: 컴포넌트에 useReducer 구현
이제 정의된 리듀서와 초기 상태를 실제 React 컴포넌트에 적용해 보겠습니다.
import React, { useReducer } from 'react';
function ShoppingCart() {
const [cartState, dispatch] = useReducer(cartReducer, initialState);
const addItemToCartHandler = (item) => {
dispatch({ type: 'ADD_ITEM', item });
};
const removeItemFromCartHandler = (id) => {
dispatch({ type: 'REMOVE_ITEM', id });
};
return (
<div>
<h2>장바구니</h2>
<ul>
{cartState.items.map(item => (
<li key={item.id}>
{item.name} - ${item.price} x {item.quantity}
<button onClick={() => removeItemFromCartHandler(item.id)}>제거</button>
</li>
))}
</ul>
<div>총액: ${cartState.totalAmount.toFixed(2)}</div>
</div>
);
}
export default ShoppingCart;
ShoppingCart 컴포넌트에서는 useReducer 훅을 사용하여 cartState와 dispatch 함수를 가져옵니다. addItemToCartHandler와 removeItemFromCartHandler 함수는 각각 dispatch 함수를 호출하여 'ADD_ITEM' 또는 'REMOVE_ITEM' 액션을 리듀서로 보냅니다. 이 액션에 따라 리듀서는 상태를 업데이트하고, 업데이트된 cartState는 UI에 자동으로 반영됩니다.
이 예제를 통해 우리는 다음과 같은 점을 알 수 있습니다:
- 초기 카트 구조는 항목 배열과 총액을 포함하도록 정의되었습니다.
- 리듀서 함수는 항목 추가/제거 로직과 함께 총액을 자동으로 재계산하는 기능을 담당합니다.
ShoppingCart컴포넌트는dispatch()함수를 호출하여 카트 내용을 수정하라는 요청을 보낼 뿐, 실제 상태 변경 로직은 리듀서에 위임하고 있습니다.
결론
'useReducer' 훅은 React 애플리케이션 내에서 복잡한 상태를 효과적으로 관리하는 데 매우 강력한 도구입니다. 모든 관련 로직을 한 곳에 중앙 집중화함으로써 디버깅을 더욱 쉽게 만들고, UI 렌더링과 비즈니스 로직 처리 간의 관심사를 분리하여 컴포넌트를 깔끔하게 유지하는 데 도움을 줍니다.
단순한 토글이나 카운터와 같은 간단한 상태 변화에는 'useState'가 충분하지만, 여러 데이터 조각이나 사용자 입력 간의 미묘한 상호 작용을 요구하는 더 정교한 애플리케이션을 구축할 때 'useReducer' 패턴을 숙달하는 것은 개발자로서 당신에게 큰 도움이 될 것입니다. 'useReducer'를 통해 더욱 견고하고 유지보수하기 쉬운 React 애플리케이션을 만들어나가세요!
'프로그래밍 > ReactJS' 카테고리의 다른 글
| React Props 완전 정복: 컴포넌트 간 데이터 전달의 핵심! (0) | 2025.10.09 |
|---|---|
| 리액트 Context API: 복잡한 상태 관리, 이제는 우아하게 해결하자! (0) | 2025.10.09 |
| React 개발의 핵심: useState 훅 완벽 가이드 (0) | 2025.10.09 |
| React 컴포넌트 마스터하기: 함수형과 클래스형, 그리고 훅의 모든 것! (0) | 2025.10.08 |
| React 컴포넌트 생명주기: 클래스에서 훅까지, 완벽 이해 가이드 (0) | 2025.10.08 |