Conversation
| // 2. 특정 날짜 페이지에 노출 여부 결정 | ||
| export const isVisibleOnDate = (todo: Todo, targetDate: string): boolean => { | ||
| const start = todo.createdAt; | ||
| const end = todo.dueDate; | ||
|
|
||
| if (!end) return start === targetDate; | ||
| return targetDate >= start && targetDate <= end; | ||
| }; |
There was a problem hiding this comment.
안녕하세요 윤서님~! 2주차 과제하시느라 고생 많으셨습니당!
전체적으로 함수명이 직관적이라서 코드를 읽을 때 목적을 확인하기에 좋았습니다.
그리고 코드 구조도 깔끔하고 컴포넌트 분리도 잘 되어 있어서 읽기에 편했습니다!
| /* 2. 미디어 쿼리 (시스템 다크 모드 대응) */ | ||
| /* 기본값 아래에 있어야 다크 모드일 때 값을 덮어씁니다. */ | ||
| @media (prefers-color-scheme: dark) { | ||
| :root { | ||
| --bg: #1a1c2c; | ||
| --text: #e0e0ff; | ||
| --container: #252a41; | ||
| --btn: #3f51b5; | ||
| --accent: #6874ad; | ||
| --delete: #a180ad; | ||
| --delete-hover: #7b5e8c; | ||
| --add: #67beb5; | ||
| --add-hover: #409c93; | ||
| } | ||
| } | ||
|
|
||
| /* 3. 특정 테마 클래스 (수동 전환용) */ | ||
| [data-theme="blueberry"] { | ||
| --bg: #1a1c2c; | ||
| --text: #e0e0ff; | ||
| --container: #252a41; | ||
| --btn: #3f51b5; | ||
| --accent: #6874ad; | ||
| --delete: #a180ad; | ||
| --delete-hover: #7b5e8c; | ||
| --add: #67beb5; | ||
| --add-hover: #409c93; | ||
| } |
There was a problem hiding this comment.
이 코드에서 미디어 쿼리 기반 다크모드와 [data-theme ] 기반 수동 테마를 함께 사용하여 시스템 설정과 사용자 선택 테마를 모두 대응할 수 있도록 설계된 점이 인상적이었습니다! 또한 :root 에서 색상 값을 변수로 관리하여 UI 전반에서 일관된 스타일을 유지할 수 있도록 하신 점이 좋았습니다. 추후 확장 시에도 최소한의 수정으로 대응할 수 있는 구조라고 생각했습니다!
chaeyoungwon
left a comment
There was a problem hiding this comment.
과제하시느라 수고 많으셨습니다!! 😊
다만 현재 배포 링크가 권한 문제로 접근이 되지 않고 있습니다.
시크릿 모드에서 한 번 더 확인해주시면 감사드리겠습니다!
There was a problem hiding this comment.
지난 주에 드렸던 피드백을 반영해서 리드미를 정말 꼼꼼하게 작성해주셨군요.. 짱 ~ 🥹👍
| --color-btn: var(--btn); | ||
| --color-delete: var(--delete); | ||
| --color-delete-hover: var(--delete-hover); | ||
| --color-bg: var(--bg); |
There was a problem hiding this comment.
변수명이 -text, -bg처럼 다소 포괄적인 이름으로 되어 있어, 추후 확장이나 유지보수 시 혼란이 생길 수 있을 것 같습니다
현재는 규모가 작은 프로젝트라 큰 문제는 없지만, 협업을 고려해 네이밍을 조금 더 명확하게 가져가보셔도 좋을 것 같아요!
There was a problem hiding this comment.
사용하지 않는 파일은 제거해도 괜찮을 것 같아용
| onClick={() => setIsImportant(!isImportant)} | ||
| className={`text-xl text-add `} | ||
| > | ||
| {isImportant ? "★" : "☆"} |
| <input | ||
| type="text" | ||
| value={todoText} | ||
| onChange={(e) => setTodoText(e.target.value)} | ||
| onKeyDown={(e) => e.key === "Enter" && handleSubmit()} | ||
| className="flex-1 bg-transparent text-text focus:outline-none py-2" | ||
| placeholder="오늘의 할 일은?" | ||
| /> |
There was a problem hiding this comment.
현재 한글 입력 시 값이 두 번씩 등록되는 현상이 있는 것 같습니다.
이전에 말씀드렸던 KeyboardEvent.isComposing을 활용해보시면 좋을 것 같아요!
참고 링크: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/isComposing
There was a problem hiding this comment.
formatDateToISO와 formatDate 함수가 이름만 다르고 동일한 기능을 하는 것으로 보이네요 🧐
하나의 함수로 통일해 사용하는 것이 좋을 것 같습니다!
| <button | ||
| onClick={onToggle} | ||
| className={`w-14 h-8 rounded-full p-1 transition-colors duration-500 relative flex items-center bg-accent`} | ||
| > |
There was a problem hiding this comment.
승연님 코드리뷰에도 남겨드렸는데, 버튼 요소에는 cursor-pointer를 적용해주시는 걸 추천드립니다!
모든 버튼에 한 번에 적용하고 싶으시면 index.css에서 button에 스타일로 넣어주셔도 좋아요!
|
|
||
| function App() { | ||
| return ( | ||
| <div className="App"> |
There was a problem hiding this comment.
현재 App 클래스가 따로 정의되어 있지 않은 것 같은데 맞을까요 ?-?
| const handleContextMenu = (e: React.MouseEvent) => { | ||
| e.preventDefault(); | ||
| setShowDatePicker(!showDatePicker); | ||
| }; |
There was a problem hiding this comment.
우클릭으로 마감 기한을 설정하는 로직을 구현하신 점 좋습니다!
다만, 사용자가 우클릭으로 기한을 설정할 수 있다는 점을 인지하기 어려울 수 있어,
별도의 버튼으로 기능을 노출하거나 안내 문구를 추가해주시면 더 좋은 UX가 될 것 같습니다 😊
|
|
||
| days.push({ | ||
| fullDate, | ||
| dayName: ["일", "월", "화", "수", "목", "금", "토"][date.getDay()], |
There was a problem hiding this comment.
이런 배열은 상수로 분리하셔도 좋을 것 같아요-!
배포 링크
🔗투두 리스트
파일 구조
기획 단계
레이아웃 구조
주요 포인트는 두 개 입니다.
이 포인트들을 바탕으로 레이아웃을 구성했습니다.
개발 단계
1. 컴포넌트화
① 일반성(Generality)을 고려한 공통 컴포넌트 설계 (
common/)common폴더로 분리Input.tsx,Header.tsx등은 Todo 로직에 종속되지 않고 재사용 가능한 보편적인 인터페이스를 갖도록 설계② 복잡성을 고려한 역할의 분리 (
todo/)TodoList.tsx: 데이터의 흐름과 섹션 분류라는 구조적 역할TodoItem.tsx: 개별 할 일의 상태 표시2. 기능
① 기간 기반 노출 로직 (isVisibleOnDate)
② 타입 안정성 확보 및 유틸리티 분리 (date.ts)
코드의 유지보수성을 위해 any를 제거하고 로직을 분리했습니다.
③ TypeScript
Todo 인터페이스를 정교하게 다듬어
isImportant,dueDate등 데이터 누락으로 인한 런타임 에러를 차단했습니다.3. 리팩토링
1주차 PR 리뷰를 바탕으로 useMemo와 reducer를 사용해 리팩토링을 진행했습니다.
DailyProgress.tsxuseMemo를 통한 연산 결과 캐싱 -> 의존성 배열인 [todos, selectedDate]의 값이 변하지 않는 한, 이전 계산 결과를 재사용하도록 최적화
reduce 메서드를 활용한 단일 순회 로직 구현 -> 한 번의 순회로 total과 completed 카운트를 동시에 추출하는 집계 로직을 구현
TodoList.tsx✨Review Question
1. Virtual DOM
Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 재조정이라고 합니다.
이 접근방식이 React의 선언적 API를 가능하게 합니다. React에게 원하는 UI의 상태를 알려주면 DOM이 그 상태와 일치하도록 합니다. 이러한 방식은 앱 구축에 사용해야 하는 어트리뷰트 조작, 이벤트 처리, 수동 DOM 업데이트를 추상화합니다.
배치 업데이트 (Batching): 데이터가 변할 때마다 실제 화면을 매번 새로 그리는 게 아니라, 가상 DOM에서 변경 사항을 먼저 다 계산한 뒤 딱 한 번만 실제 DOM에 반영합니다.
효율적인 비교 (Diffing): '이전 가상 DOM'과 '새로운 가상 DOM'을 비교하여 정확히 바뀐 부분만 찾아내 업데이트합니다. 이를 통해 브라우저의 연산 비용을 획기적으로 줄입니다.
2. React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화
React.memo() (컴포넌트 메모이제이션)
역할: 고차 컴포넌트(HOC)로, 컴포넌트의 Props가 변하지 않았다면 리렌더링을 건너뛴다.
언제 쓰나: 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정하여 함수형 컴포넌트의 리렌더링 성능을 최적화 해줄 수 있다.
useMemo() (값의 메모이제이션)
역할: 복잡한 계산 결과(값)를 메모리에 저장해두고, 의존성 배열이 바뀔 때만 다시 계산한다.
장점: 함수 호출 시간도 세이브할 수 있고 같은 값을 props로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있다.
useCallback() (함수의 메모이제이션)
역할: 함수 자체를 메모리에 저장합니다. 리액트 컴포넌트 내부에서 선언된 함수는 리렌더링될 때마다 새로 생성되는데, 이를 방지합니다.
주의점: 자식 컴포넌트에게 함수를 Props로 넘겨줄 때, 자식의 React.memo가 깨지지 않게 하려고 주로 함께 사용합니다.
추가적인 방법
setState를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있는데, 이렇게 하면 useCallback을 사용할 때 두 번째 파라미터로 넣는 배열에 값을 넣어주지 않아도 된다.
3. React 컴포넌트 생명주기 (Lifecycle)
리액트 컴포넌트는 생성(Mount) -> 업데이트(Update) -> 제거(Unmount)의 단계를 거칩니다.
1) 마운트 (Mounting)
컴포넌트가 처음 브라우저 화면에 나타나는 단계입니다.
클래스형: constructor -> render -> componentDidMount
함수형: useEffect(() => { ... }, []) (의존성 배열이 빈 배열일 때)
주요 작업: API 호출, 이벤트 리스너 등록, 외부 라이브러리 초기화.
2) 업데이트 (Updating)
Props나 State가 바뀌어 컴포넌트가 다시 그려지는 단계입니다.
클래스형: componentDidUpdate
함수형: useEffect(() => { ... }, [deps]) (의존성 배열의 값이 바뀔 때)
주요 작업: 바뀐 값에 따른 데이터 동기화, 조건부 로직 실행.
3) 언마운트 (Unmounting)
컴포넌트가 화면에서 사라지기 직전 단계입니다.
클래스형: componentWillUnmount
함수형: useEffect 내부의 return () => { ... } (Clean-up 함수)
주요 작업: 이벤트 리스너 제거, 타이머 중단(clearTimeout), 메모리 정리.