Conversation
|
🔗배포 링크. |
수정 완료했습니다. 확인 부탁드릴게용 |
| // CRUD함수를 useCallback으로 구현(useState를 사용하면 리렌더링이 자주 발생하는 문제가 생김. 따라서 자식 컴포넌트에 props로 넘겨주기만 하는 useCallback 사용) | ||
| const addTodo = useCallback((date: string, text: string) => { | ||
| const newTodo: Todo = { | ||
| id: crypto.randomUUID(), // 이거 다른분 1주차 코드리뷰때 있던것같아서 Date.now()로 안하고 UUID함수 썼습니다. | ||
| title: text, | ||
| isCompleted: false, | ||
| }; | ||
|
|
||
| setTodos((prev) => ({ | ||
| ...prev, | ||
| [date]: [...(prev[date] || []), newTodo], | ||
| })); | ||
| }, []); | ||
|
|
||
| const toggleTodo = useCallback((date: string, id: string) => { | ||
| setTodos((prev) => ({ | ||
| ...prev, | ||
| [date]: prev[date].map((todo) => todo.id === id ? {...todo, isCompleted: !todo.isCompleted} : todo) | ||
| })); | ||
| }, []); | ||
|
|
||
| const deleteTodo = useCallback((date: string, id: string) => { | ||
| setTodos((prev) => ({ | ||
| ...prev, | ||
| [date]: prev[date].filter((todo) => todo.id !== id) | ||
| })); | ||
| }, []); |
There was a problem hiding this comment.
CRUD 함수를 useCallback으로 관리한 점이 인상적이었습니다! 자식 컴포넌트에 전달되는 함수의 참조를 안정적으로 유지하려는 의도가 잘 보였던 것 같아요.
| [date]: [...(prev[date] || []), newTodo], | ||
| })); | ||
| }, []); | ||
|
|
There was a problem hiding this comment.
toggleTodo, deleteTodo에서 prev[date]를 바로 사용하고 있어서, 해당 날짜에 데이터가 없을 경우 map이나 filter사용 시 에러가 날 수도 있을 것 같아요. addTodo에서 하신 것처럼 (prev[date] || [])로 처리하면 더 안전할 것 같아요..!
| } | ||
|
|
||
| return { isDarkMode, toggleDarkMode }; | ||
| } No newline at end of file |
There was a problem hiding this comment.
커스텀 훅(useTodos, useDarkMode)으로 로직을 분리해두신 점이 좋았습니다.
todo 관리 로직과 UI가 분리되어 있어서 App 컴포넌트가 깔끔해 보이고, 흐름을 이해하기도 쉬웠던 것 같아요!
ryu-won
left a comment
There was a problem hiding this comment.
TypeScript 사용, 커스텀 훅 분리, storage.ts 분리,
CRUD 함수에 useCallback 적용 등 짧은 시간인데도
완성도 높은 결과물을 보여주셨네요! 너무 고생 많으셨습니다~!
| }; | ||
|
|
||
| // React.FormEvent는 이제 사용 비권장이 됐습니다. 공식 문서는 노션에 업로드해놓을테니 확인해봐도 좋을 것 같습니다. | ||
| const handleSubmit = (e: React.SubmitEvent) => { |
There was a problem hiding this comment.
| const handleSubmit = (e: React.SubmitEvent) => { | |
| const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { |
타입을 이렇게 해주시면 좋을 것 같아요!
There was a problem hiding this comment.
There was a problem hiding this comment.
오 그렇네요 영준님이 맞네요 제가 잘못전달드렸어요! 제가 하나 배워갑니당
|
|
||
| const emptyDays = Array.from({ length: firstDayOfMonth }, (_, i) => i); | ||
| const days = Array.from({ length: daysInMonth }, (_, i) => i + 1); | ||
| const weekDays = ['일', '월', '화', '수', '목', '금', '토']; |
There was a problem hiding this comment.
weekDays 상수가 렌더링마다 재생성되어서 컴포넌트 밖으로 빼는 것이 좋을 것 같아요! 그리고 자주 사용되는 상수라면 constants 파일에 관리하시는 것이 좋구요!
| @@ -0,0 +1,13 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| const deleteTodo = useCallback((date: string, id: string) => { | ||
| setTodos((prev) => ({ | ||
| ...prev, | ||
| [date]: prev[date].filter((todo) => todo.id !== id) | ||
| })); | ||
| }, []); |
There was a problem hiding this comment.
deleteTodo에서 마지막 todo를 삭제하면 빈배열이 그대로 들어가는데 빈배열을 삭제하는 코드가 들어가면 좋을 듯합니다!
| const deleteTodo = useCallback((date: string, id: string) => { | |
| setTodos((prev) => ({ | |
| ...prev, | |
| [date]: prev[date].filter((todo) => todo.id !== id) | |
| })); | |
| }, []); | |
| const deleteTodo = useCallback((date: string, id: string) => { | |
| setTodos((prev) => { | |
| const updated = (prev[date] || []).filter((todo) => todo.id !== id); | |
| const next = { ...prev }; | |
| if (updated.length === 0) { | |
| delete next[date]; // ← 빈 배열이면 키 자체를 삭제 | |
| } else { | |
| next[date] = updated; | |
| } | |
| return next; | |
| }); | |
| }, []); |
🔗배포 링크.
과제 코드 작성을 시작하기에 앞서, 항상 그랬듯 저는 구현 사항에 대한 설계를 선행하였습니다.
객체지향적 코드 작성을 지향하는 만큼, Typescript로 작성하는 것을 선택하였습니다. 먼저 투두리스트에 꼭 필요한 것이 무엇인지를 정의하여 Todo인스턴스에 아이디, 제목, 완료 여부를, 그리고 TodoData 객체를 만들어서 연월일 타임스탬프별로 투두리스트를 정리하는 구조를 선택하였습니다.
컴포넌트로는 캘린더와 투두리스트를 화면의 반씩 나누어 구현하는 방법을 지향했고(다른 분들 과제에서 가장 깔끔했던 디자인으로부터 레퍼런스르르 얻었습니다.), 요구 사항을 조금 변형하여 다음주/이번주보다는 가시적인 캘린더 내에서 이번 달/다음 달을 구현하는 방법이 좋다고 판단하였습니다.
다른 분들 코드와 리뷰를 많이 살펴보고 개발을 진행했는데요, 원래는 맨땅에 헤딩하고 모르면 ai한테 물어보고 하는 식으로 코딩했습니다. 근데 다른 분들의 코드를 먼저 보고 핵심 개념들, 예를 들자면 객체의 id를 타임스탬프가 아닌 uuid로 설정한다던가, Date객체가 아닌 Temporal 객체를 사용해본다던가(근데 지금 js에서 공식 지원하는거 맞나요? 자동완성이 안되는 것 같았습니다) 등등.. 여러 방법을 시도해보며 코드를 작성하는 경험은 처음이었기에 굉장히 뿌듯한 시간이었습니다.
주로 혼자 개발하다 보니, 평소에는 커밋 단위를 제 임의로 설정하여 작은 기능의 구현보다는 큰 흐름에서의 구현을, 모듈 단위의 커밋보다는 컴포넌트 단위의 커밋을 주로 하였으나, 이번에는 조금 더 잘게 커밋의 단위를 쪼개보려 했습니다. 근데 조금 손에 안 익어서 자꾸 까먹게 되더라구요... 중간중간 까먹었습니다.
고도화된 팀 단위의 개발에서는 굉장히 독립적인 모듈 단위의 코드 작성과 테스트가 이뤄지는 것으로 알고 있는데, 그렇지 않았던 제가 나름 반성하게 되는 시간이었습니다. 앞으로는 최대한 명확한 단위로 깃을 사용해보도록 할게요,,..!!
깃 커밋 내역에서 reset이 있던 이유에 대해 말씀드리자면, 이전에는 없었던 오류가 생겼습니다. vite@latest로 설치한 리액트 버전이 현재 tailwindCSS의 4버전과 호환이 안되더라구요. 그래서 처음에 설치할 때 --legacy-deps 옵션을 사용해서 npm을 설치했었는데, 결국 배포 과정에서 문제가 생겼습니다. vite 버전도 바꿔보고, tailwind버전도 다운그레이드하려다가 실패도 해보고 했는데 이게 수습이 안돼서 중간에 되돌렸어요.
한 번도 다크모드를 만들어 본 적이 없었는데, 이번에 여러 컴포넌트에 다크모드를 사용해보게 되면서 느낀 점은, 결국 레이아웃을 '변화를 잘 받아들일 수 있게끔 설계하는 것'이 중요하다는 것이었습니다. 반응형도 그렇고, 다크모드도 그렇고, 한 컴포넌트 내에서 여러 CSS옵션들이 기준 없이 섞이다 보면 나중에 화면이 줄어든다던가 다크모드로 바꿔야 한다던가 하는 상황에서 굉장히 골치아플 것 같다는 생각을 하게 되었습니다.