Conversation
| }; | ||
|
|
||
| return ( | ||
| <form onSubmit={handleSubmit} className="mt-6 flex gap-2"> |
There was a problem hiding this comment.
나중에 타입스크립트를 사용하실때 이벤트의 타입도 정의해야 할텐데, 현재 React.FormEvent가 사용 비권장 사양이 되었기 때문에 React.SubmitEvent로 정의하셔야 안전하다는 걸 미리 말씀드립니다.
https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forms_and_events/
| <TodoInput | ||
| ref={inputRef} | ||
| value={inputValue} | ||
| onChange={setInputValue} |
There was a problem hiding this comment.
지금 구조를 보면 App 컴포넌트에서 inputValue 상태를 관리하고 있어서, 할 일 입력창에 글자를 하나하나 입력할 때마다 App이 리렌더링되고 하위 자식 컴포넌트들도 리렌더링되고 있어요.
최적화 측면에서 입력 상태를 App에서 관리하기보다는 더 작은 컴포넌트로 내려서 입력 시 해당 컴포넌트만 리렌더링되도록 개선할 수 있을 것 같습니다!
| @@ -0,0 +1,26 @@ | |||
| export default function SummaryPanel({ | |||
There was a problem hiding this comment.
SummaryPanel 컴포넌트는 원시값 props만 받기 때문에 React.memo 적용하면 불필요한 리렌더링을 줄여볼 수 있을 것 같습니다!
ryu-won
left a comment
There was a problem hiding this comment.
커스텀 훅 분리, 유틸 함수 분리, 주간/일간 보기 전환 기능 등 짧은 시간인데 높은 완성도를 보여주셨네용~! 너무 고생하셨습니다! 디자인도 너무 예쁜 투두네요
| <ul className="mt-6 space-y-3"> | ||
| {todos.map((todo) => ( | ||
| <TodoItem | ||
| key={`${todo.dateKey}-${todo.id}`} |
| const handleDeleteTodo = (dateKey, todoId) => { | ||
| setTodoData((prev) => ({ | ||
| ...prev, | ||
| [dateKey]: (prev[dateKey] || []).filter((todo) => todo.id !== todoId), | ||
| })); | ||
| }; |
There was a problem hiding this comment.
삭제 후 빈 배열이 localStorage에 남아요! 빈 배열이 되면 키 자체를 지워주는 처리가 있으면 더 좋을 것 같습니다~!
| @@ -0,0 +1,13 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
| useOutsideClick(menuRef, () => { | ||
| setIsMenuOpen(false); | ||
| }); | ||
|
|
There was a problem hiding this comment.
setIsMenuOpen 함수는 리렌더링시에 계속 새로 만들어져서 useCallback으로 감싸서 전달해도 좋을 것 같아요~!
베포 링크: https://react-todo-23rd-seven.vercel.app/
기획 단계
이번 과제에서는 새로운 기능을 추가하기보다, 기존 Vanilla Todo의 핵심 흐름을 React 방식으로 재구성하는 데 집중했다. 특히 Daily / Weekly 뷰 전환을 중심으로, 날짜 기반의 할 일 관리 경험을 단순하고 직관적으로 만드는 것을 목표로 했다.
또한 이전 과제에서 받았던 피드백을 반영하며 구조와 사용자 경험을 개선했다. 특히 Weekly 뷰에서 날짜 기준으로 삭제 대상이 달라지는 버그를 수정하고자 하였다.
개발 단계
상태 구조와 컴포넌트 분리
화면을 구성하는 요소와 로직을 분리하기 위해, 렌더링 역할은 components로, 재사용 가능한 상태 로직(localStorage 연동, 외부 클릭 감지)은 hooks로, 날짜 계산과 데이터 가공은 utils로 분리했다. 기능별 책임을 명확히 나누는 것이 이후 수정 과정에서 특히 도움이 되었다.
이번 프로젝트에서는 앱의 핵심 상태를
App컴포넌트에서 한 번에 관리했다.관리한 상태는 현재 날짜(
currentDate), 뷰 모드(viewMode), 입력값(inputValue), 메뉴 열림 여부(isMenuOpen), 그리고 날짜별 todo 데이터를 담는todoData이다.또한 전체 투두 수, 완료 수, 달성률처럼 원본 상태로부터 계산할 수 있는 값은 별도의 state로 저장하지 않고 파생 데이터로 처리해 상태를 단순하게 유지했다.
<상태 구조>
currentDate: 현재 보고 있는 날짜viewMode:daily/weeklyinputValue: 입력창 값isMenuOpen: 메뉴 열림 여부todoData: 날짜별 todo 데이터<컴포넌트>
Header: 현재 뷰와 메뉴 전환DateNavigator: 날짜/주 이동TodoInput: 입력과 추가SummaryPanel: 통계 요약TodoList: Daily / Weekly 분기TodoItem: 개별 todo 렌더링핵심 기능 구현
getWeeklyTodoGroups를 통해 주간 날짜 배열을 만들고, 해당 날짜별 todo를 그룹으로 묶었다.useLocalStorage커스텀 훅을 만들었다.dateKey전달todo.id만으로는 부족했다.dateKey까지 함께 전달해 각 날짜 배열에서 정확히 수정/삭제되도록 구조를 바꿨다.1주차 피드백 반영
느낀점 및 배운점
이번 미션을 하면서 가장 크게 느낀 점은, 같은 Todo 앱이라도 Vanilla JS로 구현할 때와 React로 구현할 때 문제를 바라보는 방식이 많이 다르다는 점이었다. 1주차에는 날짜가 바뀌거나 뷰가 전환될 때마다 어떤 부분을 직접 다시 그려야 하는지 계속 신경 써야 했다면, 이번에는 상태를 기준으로 화면이 자동으로 바뀌는 구조로 바꾸면서 React가 왜 필요한지 직접 느낄 수 있었다.
리액트를 이번에 처음 접하다 보니
useState,useRef,forwardRef같은 훅들을 어떤 상황에서 써야 하는지 이해하고, 각 상태와 요소들을 연결해 관리하는 과정이 가장 헷갈리고 어렵게 느껴졌다. 특히 이번에는 이전 과제에서 받았던 피드백을 실제 코드 구조에 반영하는데 집중하였는데, 메뉴 버튼처럼 단순해 보이는 기능도, 바깥 영역을 클릭했을 때 닫히도록 구현하려면 ref로 영역을 잡고 이벤트를 따로 감지해야 해서 생각보다 까다로웠다. 어떻게 해야 상태와 흐름을 효율적이고 구조적으로 관리할 수 있는지 고민하는 시간이 길었던 것 같다.가장 많은 시간을 투자한 부분은 Weekly 뷰에서 날짜가 다른 todo가 삭제되지 않던 버그를 수정하는 과정이었는데, 처음에는 삭제 로직이 현재 보고 있는 날짜(
currentDate)의 todo 배열만 기준으로 동작하도록 구현되어 있었기 때문에, Weekly처럼 여러 날짜의 todo를 한 화면에 모아 보여주는 경우에는 현재 날짜에 해당하지 않는 todo를 눌러도 삭제가 되지 않았다. 화면상으로는 같은 리스트 안에 보이는데 실제로는 참조하는 데이터가 달라서 생기는 문제였기 때문에, 처음에는 왜 특정 todo만 삭제가 안 되는지 파악하기가 힘들었다.문제는 Weekly 뷰에서는
todo.id만으로는 데이터에 정확하게 접근할 수 없다는 점이었다 . 그래서 삭제와 토글 로직에todo.id뿐 아니라 해당 todo가 속한 날짜의dateKey까지 함께 전달하는 방식으로 구조를 바꾸었고, Weekly에서도 날짜가 다른 todo를 정상적으로 수정하고 삭제할 수 있었다. 이 과정을 통해 단순히 한 화면만 보고 기능을 테스트하는 것에는 한계가 있으며, 그 화면이 실제로 어떤 데이터 구조와 연결되어 있는지까지 생각해야 한다는 것을 알았다.또한 컴포넌트를 역할별로 나누고, hooks와 utils를 분리해 보면서 유지보수성의 중요성도 배울 수 있었다. 처음에는 파일이 많아지는 것이 복잡하게 느껴졌지만, 수정이 많아질수록 어느 파일에서 어떤 역할을 맡고 있는지가 분명해서 편리하다는 것을 알 수 있었다. 특히 localStorage 처리나 outside click 감지처럼 반복 가능한 로직을 커스텀 훅으로 분리해두니, 화면 코드를 깔끔하게 관리할 수 있었다.
Review Questions
Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
Virtual DOM: 실제 브라우저 DOM을 메모리상에 가볍게 표현한 가상 객체React는 state나 props가 변경되면 실제 DOM을 바로 수정하지 않고, 먼저 새로운 Virtual DOM을 만든 뒤 이전 Virtual DOM과 비교하여 변경된 부분만 실제 DOM에 반영한다.
실제 DOM 조작은 비용이 큰데, DOM이 변경되면 브라우저는 레이아웃 계산, 리페인트, 리플로우 등의 작업을 수행할 수 있기 때문에, 불필요한 DOM 조작이 많아질수록 성능에 부담이 생긴다. 따라서 Virtual DOM은 이러한 부담을 줄이기 위해 변경 사항을 먼저 가상으로 계산한 뒤 필요한 부분만 실제로 반영한다.
이점:
React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.
React는 state나 props가 바뀌면 컴포넌트를 다시 렌더링한다. 이 과정 자체는 자연스럽지만, 불필요한 재렌더링이 반복되면 성능이 저하될 수 있다. 따라서 이를 줄이기 위해 React.memo(), useMemo(), useCallback() 같은 도구를 사용할 수 있다.
React 컴포넌트 생명주기에 대해서 설명해주세요.
React 컴포넌트 생명주기란 컴포넌트가 생성되고, 변경되고, 제거되는 전체 과정을 의미한다. 보통 Mounting, Updating, Unmounting 3단계가 있다.
useEffect(() => { ... }, [])를 사용하여 컴포넌트가 처음 마운트된 뒤 한 번만 실행되어야 하는 로직을 처리한다.useEffect(() => { ... }, [의존성])형태를 사용하여 특정 값이 바뀔 때마다 실행되는 로직을 작성할 수 있다.useEffect의 cleanup 함수를 통해 이러한 정리 작업을 처리한다.