반응형
React로 애플리케이션을 개발하다 보면, 간단한 상태 관리에는 useState가 적합하지만, 상태의 구조가 복잡하거나 여러 상태가 서로 의존성을 가질 때는 보다 강력한 도구가 필요합니다. 이럴 때 유용한 Hook이 바로 useReducer입니다. 이번 글에서는 useReducer를 활용하여 복잡한 상태를 효율적으로 관리하는 방법을 살펴보겠습니다.
1. useReducer란 무엇인가?
useReducer는 Redux와 유사한 개념으로 동작하는 React의 상태 관리 Hook입니다. 복잡한 상태를 "액션(action)"과 "리듀서(reducer)"로 구조화하여 관리할 수 있도록 돕습니다.
- 리듀서 함수(reducer function): 현재 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수입니다.
- 액션(action): 상태 변경의 의도를 설명하는 객체로, 일반적으로 type 필드를 포함합니다.
- 현재 상태와 디스패치 함수: useReducer는 현재 상태와 상태를 업데이트하는 디스패치(dispatch) 함수를 반환합니다.
const [state, dispatch] = useReducer(reducer, initialState);
2. 왜 useReducer를 사용하는가?
useReducer는 다음과 같은 경우에 유용합니다:
- 상태가 다차원적이거나 복잡한 경우: 단순한 불리언이나 문자열보다 여러 값이 관련된 객체 상태를 관리해야 할 때 유용합니다.
- 상태 전환이 복잡한 로직을 필요로 할 때: 여러 상태 변경이 논리적으로 연결된 경우에도 적합합니다.
- 컴포넌트가 점점 커질 때: 복잡한 상태 관리는 useReducer로 별도 로직을 분리함으로써 코드 가독성을 높일 수 있습니다.
3. 기본적인 useReducer 사용법
다음은 useReducer의 기본적인 사용 예제입니다.
초기 상태와 리듀서 함수 정의
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
컴포넌트에서 useReducer 사용
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
이 간단한 카운터 예제는 다양한 상태 변경 동작을 깔끔하게 처리합니다.
4. 복잡한 상태 관리 예제: Todo 리스트 구현
초기 상태와 리듀서 함수 작성
const initialState = {
todos: [],
};
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
todos: [...state.todos, { id: action.id, text: action.text, completed: false }],
};
case 'toggle':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
),
};
case 'delete':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id),
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
Todo 컴포넌트 작성
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const [text, setText] = React.useState('');
const handleAdd = () => {
if (text.trim()) {
dispatch({ type: 'add', id: Date.now(), text });
setText('');
}
};
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter a new task"
/>
<button onClick={handleAdd}>Add</button>
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
}}
onClick={() => dispatch({ type: 'toggle', id: todo.id })}
>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'delete', id: todo.id })}>Delete</button>
</li>
))}
</ul>
</div>
);
}
이 Todo 리스트는 상태가 복잡하지만 useReducer를 통해 잘 구조화된 상태 관리가 가능합니다.
5. useReducer 사용 시 고려사항
- 상태가 정말 복잡한가? 단순한 상태 관리에는 useState가 더 적합할 수 있습니다.
- 리듀서 함수는 순수해야 한다 리듀서 내부에서 외부 상태를 수정하거나 비동기 작업을 수행하지 않아야 합니다.
- 상태 로직을 테스트하기 쉽게 만든다 리듀서 함수는 독립적으로 테스트하기 쉬운 형태로 작성할 수 있습니다.
- Context와 함께 사용하기 글로벌 상태 관리가 필요하다면 useReducer와 Context API를 함께 사용하는 것을 고려해보세요.
6. 결론
useReducer는 React에서 복잡한 상태를 효과적으로 관리할 수 있는 강력한 도구입니다. 상태 전환 로직을 한 곳에 모으고 컴포넌트의 코드 복잡도를 줄이는 데 유용합니다. 프로젝트의 요구사항에 따라 useReducer를 적절히 활용하여 효율적인 상태 관리를 경험해보세요.
반응형
'React' 카테고리의 다른 글
React에서 Prop Drilling 문제 해결하기 (0) | 2024.12.13 |
---|---|
React에서 Context와 Redux 비교하기 (0) | 2024.12.13 |
React에서 useContext로 전역 상태 관리하기 (0) | 2024.12.12 |
React에서 Suspense 사용법 (0) | 2024.12.12 |
React에서 useState로 카운터 만들기 (0) | 2024.12.12 |