4์ฃผ์ฐจ๋ ํด๋ก๋ ์ฝ๋๋ฅผ ์ฌ์ฉํด์ ํฌ๋์ฑ๊ณผ ๊ฐ๋จํ ๊ฒ์ ์ฑ์ ๋ง๋๋ ๊ฑธ ํด๋ดค๋ค. ํด๋ก๋ ์ฝ๋๋ ํฐ๋ฏธ๋์์ ์์ ํด์ผ๋์ ์์ง ์ ์์ด ์ ์๋๋ค. ํฌ๋ ๊ฐ์ ๊ฒฝ์ฐ๋ ํ์ฌ ๋ด ๊ฐ์ธ ํฌํธํด๋ฆฌ์ค๋ก ์ฌ์ฉํ๋ ค๊ณ ๋ง๋ค๊ณ ์๋๊ฒ ์์ด์ ๊ทธ๊ฑธ ๋ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก ์ฌ์ฉํด๋ดค๋ค. ์ฌ์ค ํ๋ก ํธ ๋ถ๋ถ์ ํด๋ก๋ ์น์์ ๋ง๋ ๊ฑฐ๋ผ์ ๊ฐ์ ์ฌํญ์ด ์์ ์ค ์์๋๋ฐ ์์ ํด์ผ ๋ ๋ถ๋ถ์ด ๋๋ฌด ๋ง์ด ๋์๋ค..ใ ใ ํ๋ก ํธ ์ด๋ ค์...
๊ทธ๋์ ํด๋ก๋ ์ฝ๋์ ๊ฐ์ ์์ ํ ์ ์๋๋ก ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋ง๋ค์ด์ ๋ฌ๋ผ๊ณ ํ๋ค๋ ์๋์ฒ๋ผ ๋ฆฌ์คํธ๋ฅผ ์คฌ๋ค.
๐ ๏ธ React ํ๋ก์ ํธ ๊ฐ์ ์ฒดํฌ๋ฆฌ์คํธ
๐ ํ๋ก์ ํธ: Productivity App (Todo + Pomodoro)
๐ด ๋์ ์ฐ์ ์์ (๋ณด์/๊ธฐ๋ฅ ๋ฌธ์ )
1. Protected Route ์ถ๊ฐ
- [ ] ProtectedRoute.jsx ์ปดํฌ๋ํธ ์์ฑ
- [ ] ๋ก๊ทธ์ธ ์ ํ ์ฌ์ฉ์๊ฐ /todo ์ ๊ทผ ์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- [ ] App.jsx์์ TodoPage๋ฅผ ProtectedRoute๋ก ๊ฐ์ธ๊ธฐ
// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { selectIsAuthenticated } from '../store/authSlice';
export function ProtectedRoute({ children }) {
const isAuthenticated = useSelector(selectIsAuthenticated);
return isAuthenticated ? children : <Navigate to="/" replace />;
}
2. handleToggleTodo ์๋ฒ ๋๊ธฐํ
- [ ] ์ฒดํฌ๋ฐ์ค ํ ๊ธ ์ ๋ฐฑ์๋ API ํธ์ถ ์ถ๊ฐ
- [ ] ๋๊ด์ ์ ๋ฐ์ดํธ(Optimistic Update) ์ ์ฉ
- [ ] ์๋ฌ ์ ๋กค๋ฐฑ ์ฒ๋ฆฌ
const handleToggleTodo = async (id) => {
const todo = todos.find(t => t.id === id);
const previousTodos = [...todos];
// ๋๊ด์ ์
๋ฐ์ดํธ
setTodos(todos.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
));
try {
await axiosInstance.patch(`/todos/${id}`, {
completed: !todo.completed
});
} catch (error) {
// ์๋ฌ ์ ๋กค๋ฐฑ
setTodos(previousTodos);
alert('๋ณ๊ฒฝ ์คํจ');
}
};
3. 401 ์๋ฌ ์ ์๋ ๋ก๊ทธ์์
- [ ] axios.js์์ 401 ์๋ฌ ์ logout ๋์คํจ์น
- [ ] ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
// api/axios.js ์์
import { logout } from '../store/authSlice';
if (error.response?.status === 401) {
store.dispatch(logout());
window.location.href = '/';
}
๐ก ์ค๊ฐ ์ฐ์ ์์ (์ฝ๋ ํ์ง)
4. TodoPage ์ปดํฌ๋ํธ ๋ถ๋ฆฌ
- [ ] components/PomodoroTimer.jsx ๋ถ๋ฆฌ
- [ ] components/PomodoroModal.jsx ๋ถ๋ฆฌ
- [ ] components/TodoList.jsx ๋ถ๋ฆฌ
- [ ] components/TodoItem.jsx ๋ถ๋ฆฌ
- [ ] components/AddTodoModal.jsx ๋ถ๋ฆฌ
- [ ] ์ปค์คํ ํ ์ผ๋ก ๋ก์ง ๋ถ๋ฆฌ: hooks/usePomodoro.js
๋ชฉํ: TodoPage.jsx๋ฅผ 528์ค → 100์ค ์ดํ๋ก
5. ๋ก๋ฉ/์๋ฌ ์ํ ๊ด๋ฆฌ
- [ ] API ํธ์ถ ์ ๋ก๋ฉ ์ํ ์ถ๊ฐ
- [ ] ๋ก๋ฉ ์คํผ๋ ์ปดํฌ๋ํธ ์์ฑ
- [ ] ์๋ฌ ๋ฐ์ ์ ์ฌ์ฉ์ ์นํ์ ์๋ฌ ๋ฉ์์ง ํ์
- [ ] ๋น ์ํ(Empty State) UI ๊ฐ์
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const fetchTodos = async () => {
setIsLoading(true);
setError(null);
try {
const response = await axiosInstance.get('/todos');
setTodos(response.data);
} catch (err) {
setError('ํ ์ผ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค.');
} finally {
setIsLoading(false);
}
};
6. useEffect ์์กด์ฑ ๋ฌธ์ ํด๊ฒฐ
- [ ] handlePomodoroComplete๋ฅผ useCallback์ผ๋ก ๊ฐ์ธ๊ธฐ
- [ ] useEffect ์์กด์ฑ ๋ฐฐ์ด ์ ๋ฆฌ
- [ ] ESLint exhaustive-deps ๊ฒฝ๊ณ ํด๊ฒฐ
const handlePomodoroComplete = useCallback(async () => {
// ๊ธฐ์กด ๋ก์ง
}, [selectedTodo, pomodoroMode]);
useEffect(() => {
// ...
}, [isRunning, pomodoroTime, pomodoroMode, handlePomodoroComplete]);
7. DOM ์ง์ ์ ๊ทผ ์ ๊ฑฐ
- [ ] document.getElementById ์ ๊ฑฐ
- [ ] useRef ๋๋ ์ํ(state)๋ก ๋ณ๊ฒฝ
// Before
const select = document.getElementById('todo-select');
handleStartPomodoroFromCenter(select.value);
// After
const [selectedTodoId, setSelectedTodoId] = useState('');
<select
value={selectedTodoId}
onChange={(e) => setSelectedTodoId(e.target.value)}
>
๐ข ๋ฎ์ ์ฐ์ ์์ (์ผ๊ด์ฑ/์ ๋ฆฌ)
8. ์์ด์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํต์ผ
- [ ] ์ด๋ชจ์ง vs lucide-react ์ค ํ๋ ์ ํ
- [ ] LoginPage ์์ด์ฝ ์์ (ํ์ฌ: ์ด๋ชจ์ง)
- [ ] SignupPage์ ํต์ผ
9. console.log ์ ๊ฑฐ
- [ ] TodoPage.jsx 349์ค: console.log('Todo:', todo) ์ ๊ฑฐ
- [ ] TodoPage.jsx 215์ค: console.log('๐ Calling backend:...') ์ ๊ฑฐ
- [ ] TodoPage.jsx 258์ค: console.log('โฐ Timer complete!...') ์ ๊ฑฐ
- [ ] ๋ฐฐํฌ ์ ๋ชจ๋ console.log ์ ๋ฆฌ
10. Deprecated API ๊ต์ฒด
- [ ] onKeyPress → onKeyDown์ผ๋ก ๋ณ๊ฒฝ
- LoginPage.jsx (91์ค, 107์ค)
- SignupPage.jsx (89์ค, 105์ค, 120์ค)
// Before
onKeyPress={handleKeyPress}
// After
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
11. Header ์ปดํฌ๋ํธ ์์ฑ
- [ ] ๋น์ด์๋ Header.jsx ๊ตฌํ ๋๋ ์ญ์
- [ ] ํ์์ ๊ณตํต ๋ค๋น๊ฒ์ด์ ์ถ๊ฐ
12. alert() → ํ ์คํธ ๋ฉ์์ง๋ก ๋ณ๊ฒฝ
- [ ] react-toastify ๋๋ ์ปค์คํ ํ ์คํธ ์ ์ฉ
- [ ] ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์
๐ง ์ถ๊ฐ ๊ถ์ฅ์ฌํญ
ํด๋ ๊ตฌ์กฐ ๊ฐ์
src/
โโโ api/
โ โโโ axios.js
โโโ components/
โ โโโ common/
โ โ โโโ Button.jsx
โ โ โโโ Input.jsx
โ โ โโโ Modal.jsx
โ โ โโโ LoadingSpinner.jsx
โ โโโ todo/
โ โ โโโ TodoList.jsx
โ โ โโโ TodoItem.jsx
โ โ โโโ AddTodoModal.jsx
โ โโโ pomodoro/
โ โโโ PomodoroTimer.jsx
โ โโโ PomodoroModal.jsx
โโโ hooks/
โ โโโ usePomodoro.js
โ โโโ useTodos.js
โโโ pages/
โ โโโ LoginPage.jsx
โ โโโ SignupPage.jsx
โ โโโ TodoPage.jsx
โโโ store/
โ โโโ store.js
โ โโโ authSlice.js
โโโ utils/
โโโ formatTime.js
TypeScript ๋์ ๊ณ ๋ ค
- [ ] ํ์ ์์ ์ฑ ํ๋ณด
- [ ] ์๋์์ฑ ๋ฐ ๋ฆฌํฉํ ๋ง ํธ์์ฑ
ํ ์คํธ ์ถ๊ฐ
- [ ] Jest + React Testing Library ์ค์
- [ ] ์ฃผ์ ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ
- [ ] API ํธ์ถ ๋ชจํน ํ ์คํธ
๐ ์งํ ์ํฉ
ํญ๋ชฉ์ํ์๋ฃ์ผ
| Protected Route | โฌ | - |
| ์๋ฒ ๋๊ธฐํ | โฌ | - |
| ์๋ ๋ก๊ทธ์์ | โฌ | - |
| ์ปดํฌ๋ํธ ๋ถ๋ฆฌ | โฌ | - |
| ๋ก๋ฉ ์ํ | โฌ | - |
| useEffect ์์ | โฌ | - |
| DOM ์ ๊ทผ ์ ๊ฑฐ | โฌ | - |
| ์์ด์ฝ ํต์ผ | โฌ | - |
| console.log ์ ๊ฑฐ | โฌ | - |
| onKeyPress ๊ต์ฒด | โฌ | - |
| Header ์์ฑ | โฌ | - |
| ํ ์คํธ ๋ฉ์์ง | โฌ | - |
๋์์ธ ์ ๋ ์์ ํ๋ ค๊ณ ํ๋๋ฐ ๋ฌธ์ ๊ฐ ๋ง์ ๋ณด์ฌ์ vscode์์ ํ๋ก์ ํธ๋ฅผ ์ด๊ณ ํด๋ก๋ ์ฝ๋๋ฅผ ํฐ๋ฏธ๋์ ํค๊ณ 1๋ฒ๋ถํฐ ์์ ํด๊ฐ๋ค. ํด๋ก๋ ์น์์ ํด๋ก๋ ์ฝ๋์์ ์ฒดํฌ๋ฆฌ์คํธ 1๋ฒ์ ์์ ํ ์ ์๋๋ก ๋ช ๋ น์ด๋ฅผ ๋ฌ๋ผ๊ณ ํ๊ณ

์๋ ๊ตฌ์ฒด์ ์ธ ํ๋กฌํํธ๋ฅผ ๋ฃ์ด๋ดค๋ค.

์ด๋ ๊ฒ ๋ณ๊ฒฝํ ๋๋ง๋ค ์ด๋ค๊ฑธ ์์ ํ๋์ง ๋ณด์ฌ์ฃผ๊ณ

์์ ๋ ๋ถ๋ถ์ ๋ฐ์ํ ๊ฑด์ง ๋ฌผ์ด๋ณธ๋ค. ๊ด์ฐฎ์๊ฑฐ ๊ฐ์์ yes ๋๋ฆ ใ ใ
์๋ ค์ค ์์ ์ฌํญ์ ๋ค ๋ฐ์ํ๊ณ ์ถ์์ง๋ง ์๊ทผ ์๊ฐ์ด ๊ฑธ๋ ค์ ์ฐ์ ๋์ ์ฐ์ ์์๋ง ๋ฐ์ํ๋ค. ๋๋ฉ์ธ๋ ์๊ณ Azure์์ ๋ฐฐํฌํ๋ ๊ฑฐ ์ฐ์ต ํด๋ณด๊ณ ์ง๊ธ ๋ง๋ ํฌ๋์ฑ ๋ฐฐํฌ๊น์ง ์ผ์ผ 1์ปค๋ฐ ํ๋ฉด์ ์ผ๋ฅธ ํฌํด ๋ง๋ค์ด์ผ์ง.

๋๊ธ