오늘은 앞서 배운 React 핵심 개념들을 활용해서 실제 Todo 앱을 만들어보겠습니다!
1. 프로젝트 구조 설계 📂
src/
├── components/
│ ├── TodoInput.js
│ ├── TodoList.js
│ ├── TodoItem.js
│ └── TodoFilters.js
├── hooks/
│ └── useTodos.js
├── contexts/
│ └── TodoContext.js
└── App.js
2. 상태 관리 설계 (Context API) 🗃️
// TodoContext.js
const TodoContext = createContext();
export function TodoProvider({ children }) {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const value = {
todos,
filter,
addTodo: (text) => {
setTodos([...todos, {
id: Date.now(),
text,
completed: false
}]);
},
toggleTodo: (id) => {
setTodos(todos.map(todo =>
todo.id === id
? {...todo, completed: !todo.completed}
: todo
));
},
// ... 다른 메서드들
};
return (
<TodoContext.Provider value={value}>
{children}
</TodoContext.Provider>
);
}
3. 커스텀 Hook 만들기 🎣
// useTodos.js
export function useTodos() {
const [todos, setTodos] = useState([]);
// 로컬 스토리지 연동
useEffect(() => {
const saved = localStorage.getItem('todos');
if (saved) {
setTodos(JSON.parse(saved));
}
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
return {
todos,
addTodo: (text) => {
setTodos(prev => [...prev, { id: Date.now(), text, completed: false }]);
},
// ... 다른 메서드들
};
}
4. 컴포넌트 구현하기 🎨
TodoInput 컴포넌트
function TodoInput() {
const [text, setText] = useState('');
const { addTodo } = useTodos();
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
addTodo(text);
setText('');
};
return (
<form onSubmit={handleSubmit} className="todo-input">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="할 일을 입력하세요"
/>
<button type="submit">추가</button>
</form>
);
}
TodoItem 컴포넌트
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>삭제</button>
</li>
);
});
5. 성능 최적화 적용 🚀
1. React.memo 사용
const TodoList = memo(function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
/>
))}
</ul>
);
});
2. useCallback으로 함수 최적화
const handleToggle = useCallback((id) => {
setTodos(todos =>
todos.map(todo =>
todo.id === id
? {...todo, completed: !todo.completed}
: todo
)
);
}, []);
6. 스타일링 💅
.todo-app {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.todo-input {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
.todo-item {
display: flex;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.todo-item.completed span {
text-decoration: line-through;
color: #888;
}
7. 애니메이션 효과 추가 ✨
import { motion, AnimatePresence } from 'framer-motion';
function TodoList({ todos }) {
return (
<ul>
<AnimatePresence>
{todos.map(todo => (
<motion.li
key={todo.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: -100 }}
>
<TodoItem todo={todo} />
</motion.li>
))}
</AnimatePresence>
</ul>
);
}
8. 실제 사용 예시 📱
function App() {
const { todos, addTodo, toggleTodo, deleteTodo } = useTodos();
return (
<div className="todo-app">
<h1>할 일 관리</h1>
<TodoInput onAdd={addTodo} />
<TodoFilters />
<TodoList
todos={todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
<div className="todo-stats">
완료: {todos.filter(t => t.completed).length} / 전체: {todos.length}
</div>
</div>
);
}
배운 점 & 주의사항 📌
상태 관리 전략
- 작은 앱은 Context로 충분
- 규모가 커지면 Redux나 Recoil 고려
성능 최적화
- 필요한 곳에만 memo 사용
- 불필요한 리렌더링 방지가 중요
사용자 경험
- 로딩 상태 처리
- 에러 처리
- 애니메이션으로 부드러운 UX 제공
다음 단계 🚀
기능 확장
- 카테고리 추가
- 마감일 설정
- 우선순위 지정
테스트 코드 작성
test('Todo 추가 기능', () => { render(<TodoApp />); const input = screen.getByPlaceholderText('할 일을 입력하세요'); fireEvent.change(input, { target: { value: '새로운 할 일' } }); fireEvent.click(screen.getByText('추가')); expect(screen.getByText('새로운 할 일')).toBeInTheDocument(); });
실제 프로젝트를 진행하면서 궁금한 점이 있으시다면 댓글로 남겨주세요! 😊
728x90
'400===Dev Library > React' 카테고리의 다른 글
React 개발자를 위한 핵심 개념 총정리 🎯 (1) | 2024.10.30 |
---|---|
What are React Hooks? (0) | 2024.06.23 |
How does the virtual DOM improve performance? (0) | 2024.06.12 |
Create a simple React component that displays a message passed as a prop. (0) | 2024.06.12 |
React Component Lifecycle Explained (1) | 2024.06.10 |