리액트는 현대 웹 개발의 중요한 도구로 자리잡았습니다. 특히, 상태 관리에 있어 다양한 방법이 있지만, 그 중 useReducer는 더 복잡한 상태 로직을 효과적으로 관리할 수 있도록 돕는 React 훅입니다. 본 강좌에서는 기본적인 To Do 앱을 사용하여 useReducer를 활용하는 방법을 구체적으로 다루겠습니다.
1. 프로젝트 구조
우선, 프로젝트의 기본 구조를 살펴보겠습니다. 일반적으로 To Do 앱의 구조는 다음과 같이 구성됩니다:
/to-do-app
|-- /src
| |-- /components
| | |-- TodoItem.js
| | |-- TodoList.js
| | └-- TodoForm.js
| |
| |-- App.js
| └-- index.js
|-- package.json
|-- README.md
위 구조에서 TodoItem
은 개별 할 일 항목을 나타내고, TodoList
는 모든 할 일 목록을 보여줍니다. TodoForm
을 사용하여 새로운 할 일을 추가합니다.
2. useReducer 소개
useReducer는 복잡한 상태를 다룰 때 유용하며, 다음과 같은 형태로 사용할 수 있습니다:
const [state, dispatch] = useReducer(reducer, initialState);
state: 현재 상태를 나타내며, dispatch: 상태 업데이트를 위한 함수입니다. reducer는 상태를 어떻게 변화시킬지를 정의하는 함수입니다.
3. 기본 To Do 앱 구현
먼저 기본 To Do 앱을 구현해 보겠습니다. 아래 코드는 간단한 리액트 앱으로, 사용자가 할 일을 추가하고 목록을 보여주는 기능을 포함합니다.
import React, { useState } from 'react';
const App = () => {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim() !== '') {
setTodos([...todos, { text: input, completed: false }]);
setInput('');
}
};
const toggleTodo = (index) => {
setTodos(todos.map((todo, i) =>
i === index ? { ...todo, completed: !todo.completed } : todo
));
};
return (
나의 To Do 리스트
setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
{todos.map((todo, index) => (
- toggleTodo(index)} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
))}
);
};
export default App;
4. useReducer로 상태 관리하기
이제 위의 코드를 useReducer로 개선해보겠습니다. 상태를 복잡하게 관리할 필요가 있을 때, useReducer가 더 유용할 수 있습니다.
import React, { useReducer } from 'react';
const initialState = { todos: [] };
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { text: action.payload, completed: false }]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map((todo, index) =>
index === action.payload ? { ...todo, completed: !todo.completed } : todo
),
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [input, setInput] = React.useState('');
const addTodo = () => {
if (input.trim() !== '') {
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
}
};
return (
나의 To Do 리스트 (useReducer)
setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
{state.todos.map((todo, index) => (
- dispatch({ type: 'TOGGLE_TODO', payload: index })} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
))}
);
};
export default App;
5. useReducer의 장점
useReducer를 사용하면, 다음과 같은 장점이 있습니다:
- 복잡한 상태 로직: 여러 상태 전환을 독립적인 행동으로 관리할 수 있어 복잡한 상태 로직을 명확하게 유지할 수 있습니다.
- 중앙 집중식 관리: 여러 상태 업데이트가 있을 때, 하나의 reducer 함수로 모든 상태 변경을 처리할 수 있습니다.
- 성능 향상: 불변성 유지가 용이하여 성능 최적화에 도움이 됩니다.
6. 컴포넌트 분리
이제 App 컴포넌트를 더 작은 컴포넌트로 나누어 코드의 가독성을 높여 보겠습니다. TodoList와 TodoForm 컴포넌트를 만들어보겠습니다.
const TodoList = ({ todos, toggleTodo }) => (
{todos.map((todo, index) => (
- toggleTodo(index)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
))}
);
const TodoForm = ({ addTodo, input, setInput }) => (
setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
);
7. 최종 코드
import React, { useReducer, useState } from 'react';
const initialState = { todos: [] };
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { text: action.payload, completed: false }]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map((todo, index) =>
index === action.payload ? { ...todo, completed: !todo.completed } : todo
),
};
default:
return state;
}
};
const TodoList = ({ todos, toggleTodo }) => (
{todos.map((todo, index) => (
- toggleTodo(index)}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
))}
);
const TodoForm = ({ addTodo, input, setInput }) => (
setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
);
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim() !== '') {
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
}
};
return (
나의 To Do 리스트 (useReducer)
dispatch({ type: 'TOGGLE_TODO', payload: index })} />
);
};
export default App;
8. 마치며
이번 강좌에서는 To Do 앱을 통해 useReducer를 적용하는 방법을 알아보았습니다. useReducer는 복잡한 상태 로직을 간결하게 관리할 수 있도록 도와주며, 가독성과 유지보수성 또한 높입니다. 더 나아가 여러 컴포넌트로 구성하여 코드의 구조를 개선할 수 있었습니다. 이러한 패턴은 더욱 복잡한 애플리케이션에서도 유용하게 사용될 수 있습니다. 다음 시간에는 useContext를 활용하여 전역 상태 관리에 대해 알아보겠습니다.