Conversation
| }; | ||
|
|
||
| const activeCount = useMemo(() => todos.filter((todo) => !todo.completed).length, [todos]); | ||
| const doneCount = useMemo(() => todos.filter((todo) => todo.completed).length, [todos]); |
There was a problem hiding this comment.
activeCount와 doneCount에서 useMemo가 각각 사용되고 있는데, 같은 todos를 순회하기 때문에 하나로 합치는 게 효율적으로 보여요!
|
|
||
| useEffect(() => { | ||
| localStorage.setItem(currentDate, JSON.stringify(todos)); | ||
| }, [todos, currentDate]); |
There was a problem hiding this comment.
currentDate가 변경될 때 localStorage.setItem Effect([todos, currentDate])도 함께 트리거되는데, 이 시점에서 todos의 값이 아직 업데이트되기 전이에요! useRef로 currentDate를 추적하고 setItem effect의 의존성에서 currentDate를 제거하면 더 안전할 것 같아요!!
chaeyoungwon
left a comment
There was a problem hiding this comment.
시간이 부족하셨을 텐데도 과제를 성실히 제출해주셔서 감사합니다 !!
추가로 개선해보시면 좋을 부분들에 대해 코멘트 드렸습니다 🙇♀️🤩
There was a problem hiding this comment.
리드미도 작성하셨네요 ㅎㅎ 아주 좋습니다 -!!
추후 과제에서도 작성하신다면, 파일 구조도 함께 정리해주시면 더 좋을 것 같아요 😊
There was a problem hiding this comment.
코드 전반적으로 hex 색상값을 직접 사용하는 부분이 보이는데, 기존 CSS에서 전역 색상 변수를 사용하신 것처럼
Tailwind에서도 theme 레이어에 --color-* 형태로 색상 토큰을 정의해보셔도 좋을 것 같습니다!
| type TodoItemProps = { | ||
| todo: Todo; | ||
| index: number; | ||
| setDragIndex: (index: number) => void; | ||
| handleDrop: (index: number) => void; | ||
| onRemoveTodo: (id: string) => void; | ||
| onToggleTodo: (id: string) => void; | ||
| }; |
There was a problem hiding this comment.
컴포넌트 props는 type으로도 정의할 수 있지만, interface로도 작성이 가능합니다!
두 방식에는 각각 차이가 있으니, 한 번 비교해보시고 상황에 맞게 어떻게 정의할지 고민해보셔도 좋을 것 같아요 !!
There was a problem hiding this comment.
현재는 프로젝트 규모가 작아 App.tsx에 로직을 함께 작성하신 것 같은데요,
앞으로는 유틸 함수와 훅을 분리하여 구조화하는 방식도 고려해보셔도 좋을 것 같아요!
(ex: 날짜 관련 유틸은 utils/dateUtils.ts에 작성 후 export하여 사용)
| @import 'tailwindcss'; | ||
| @custom-variant dark (&:where(.dark, .dark *)); | ||
| @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css'); |
| useEffect(() => { | ||
| const savedDarkMode = JSON.parse(localStorage.getItem('darkMode') || 'false') as boolean; | ||
| setDarkMode(savedDarkMode); | ||
| }, []); |
There was a problem hiding this comment.
38번줄의 setTodo와 47번줄의 setDarkMode에서
Error: Calling setState synchronously within an effect can trigger cascading renders
다음과 같은 오류가 발생하는 것을 확인하실 수 있습니다.
해당 오류는 반드시 수정해야 하는 것은 아니며, 이 오류가 발생한다고 해서 코드가 무조건 잘못된 것은 아닙니다!
다만, 상태 변경 → useEffect 실행 → 다시 상태 변경과 같은 흐름으로 이어질 경우 무한 루프가 발생할 수 있는 구조이기 때문에, 관련 공식 문서나 블로그 글을 한 번 참고해보시면 좋을 것 같습니다!
https://ko.react.dev/learn/you-might-not-need-an-effect
https://velog.io/@soyeon364/React-useEffectEvent
| ◀ | ||
| </button> | ||
|
|
||
| <button |
There was a problem hiding this comment.
버튼 요소에는 cursor-pointer를 적용해주시는 걸 추천드립니다!
모든 버튼에 한 번에 적용하고 싶으시면 index.css에서 button에 스타일로 넣어주셔도 좋아요!
| <aside | ||
| data-sidebar | ||
| className={`fixed left-0 top-0 z-50 flex h-full w-64 flex-col bg-[#fbf0d6] px-2 py-3 shadow-xl transition-transform duration-300 dark:bg-white ${ | ||
| isOpen ? 'translate-x-0' : '-translate-x-full' | ||
| }`} | ||
| > |
There was a problem hiding this comment.
앞으로 Tailwind CSS를 사용하다 보면 조건부 스타일링을 적용해야 하는 경우가 많을 텐데요,
clsx나 cn과 같은 3주차 세션 자료에 있는 유틸들을 다음 과제에서 한 번 적용해보시면 좋을 것 같습니다 -!!
| <input | ||
| type="checkbox" | ||
| checked={todo.completed} | ||
| onChange={() => onToggleTodo(todo.id)} | ||
| className="h-[18px] w-[18px] shrink-0 cursor-pointer appearance-none rounded-[5px] border-2 border-[#f9f9f9] bg-[#f9f9f9] transition-all checked:relative checked:bg-[#f9f9f9] checked:after:absolute checked:after:left-[3px] checked:after:top-[-2px] checked:after:text-sm checked:after:text-[#ff4d4d] checked:after:content-['✓']" |
There was a problem hiding this comment.
현재 체크 표시를 top, left 위치 값으로 하드코딩하여 정렬하고 있어 중앙에서 약간 어긋나 보이는 것 같아요!
flex를 활용해 중앙 정렬하는 방식으로 구현해보셔도 좋을 것 같습니당
There was a problem hiding this comment.
현재 사용되지 않는 이미지 파일들이 다수 포함되어 있는 것으로 보입니다.
추후 사용 계획이 없다면, 불필요한 파일은 정리해보시는 것을 추천드립니다!
배포링크: https://ceos-week2-react-todo-23rd-five.vercel.app/
느낀점
: 이번 과제를 진행하면서 프로젝트를 먼저 완성한 뒤, 다시 처음으로 돌아가 기능 단위로 커밋을 나누어 진행했습니다.
2주차까지는 개인 과제였지만, 앞으로 진행될 협업 과제에서는 단순한 기록이 아닌 진행 상황을 공유하기 위한 커밋이 중요하다는 점을 느끼게 되었습니다.
또한 커밋을 기능 단위로 나누는 것이 프로젝트 흐름을 이해하는 데에도 도움이 된다고 느꼈습니다.
Review Questions
- Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
: Virtual DOM은 실제 DOM을 직접 조작하지 않고, 메모리 상에 가상의 DOM을 만들어 변경된 부분만 실제 DOM에 반영하는 방식입니다.
예를 들어 Todo 하나의 상태가 변경되었을 때, 전체를 다시 렌더링하는 것이 아니라 변경된 TodoItem만 업데이트됩니다.
이를 통해 불필요한 DOM 조작을 줄일 수 있어 성능이 개선되고, 상태 기반으로 UI를 관리할 수 있어 코드 작성도 더 편해집니다.
- React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.
: React에서는 불필요한 렌더링을 줄이기 위해 다양한 최적화 방법을 제공합니다.
이 외에도 상태를 최소한으로 나누거나, 컴포넌트를 적절히 분리하는 것도 렌더링 최적화에 도움이 됩니다.
- React 컴포넌트 생명주기에 대해서 설명해주세요
: React 컴포넌트는 크게 Mount, Update, Unmount 단계로 나눌 수 있습니다.
함수형 컴포넌트에서는 useEffect를 사용하여 이러한 생명주기 흐름을 관리할 수 있습니다.
예를 들어, 특정 값이 변경될 때 localStorage를 업데이트하거나, 초기 렌더링 시 데이터를 불러오는 등의 작업을 수행할 수 있습니다.