Skip to content

[2주차] 김서연 미션 제출합니다. #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

only1Ksy
Copy link

배포 링크

React | To-do List


느낀 점

처음 리액트로 코드를 옮길 때 styled component를 사용하지 않고 전부 className으로 복잡하게 코드를 짰다가 전부 다시 옮기는 과정을 거쳤어요... 하하 과정은 너무 힘들었지만 옮겨보고 나니 styled component로 코드를 짜는 게 훨씬 가독성도 좋고 편리하다고 느껴져서 왜 사용하고 있는지 몸소 깨닫게 된 소중한 경험이었습니다 앞으로도 계속 사용하게 될 것 같아요!
또 비동기 때문에 버튼을 눌러도 UI가 한 박자 늦게 바뀌고, key 값을 index로 설정했다가 완료/미완료 리스트 사이에 key값 중복으로 로직이 꼬이는 오류 같은 자잘한 문제들을 해결하느라 힘들었는데 비슷한 문제를 겪은 분들이 계시다면 어떤 과정으로 어떻게 코드를 짜셨을지 궁금합니다! 이번에도 다른 분들 코드와 피드백을 열심히 확인해 많이 배워가야겠습니다! ㅎㅎ


Key Questions

1️⃣ Virtual-DOM이란?

Virtual-DOM

  • UI의 이상적인 표현을 메모리에 저장하고, ReactDOM과 같은 라이브러리에 의해 실제 DOM과 동기화(= 재조정)하는 프로그래밍 개념이다.
  • DOM을 JavaScript 객체로 흉내낸 것으로, 일종의 복제판이다.
  • React는 업데이트가 발생하면 실제 DOM을 수정하기 전에 가상의 복제판 DOM에 먼저 반영해 본다. (=> 일종의 Buffer 역할)

2️⃣ React.memo(), useMemo(), useCallback()

📌 React.memo()

HOC (Higher-Order Components)

  • 컴포넌트를 인자로 받아서 새로운 컴포넌트를 return 해 주는 구조의 함수

사용법

// example
const MyComponent = React.memo((props) => {
   return (/*컴포넌트 렌더링 코드*/)}
);
  • 하나의 컴포넌트가 같은 props를 넘겨받았을 때, 같은 결과를 렌더링하는 경우 React.memo를 사용하여 불필요한 컴포넌트 렌더링을 방지한다.
  • React.memo를 사용하는 경우 이전과 같은 props가 들어올 때는 렌더링 과정을 스킵하고 가장 최근에 렌더링 된 결과를 재사용한다.
  • React.memo는 넘겨받은 props의 변경 여부만을 체크한다.
  • 다만 컴포넌트 내부에서 useState 같은 훅을 사용하는 경우는 리렌더링된다.

📌 useMemo()

hook

사용법

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useMemo는 메모이즈된 값을 return한다. 이전 값을 기억해두었다가 조건에 따라 재활용하여 성능을 최적화한다.
  • 인자로 함수, Dependencies를 넘겨받으며, 이때 2번째 인자로 넘겨준 의존인자 중 하나라도 값이 변경되면 1번째 인자인 함수를 재실행한다.
  • 모든 함수를 useMemo로 감싸면 이것도 리소스가 낭비되므로, 연산량이 많은 곳에서만 사용하는 것이 좋다.

📌 useCallback()

hook

사용법

function Profile(){
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const onSave = useCallback(() => saveToServer(name, age), [name, age]);
  return (
    <div>
       <p>{`name is ${name}`}</p>
       <p>{`age is ${age}`}</p>
   <UserEdit onSave={onSave} setName={setName} setAge={setAge} />
    </div>
  );
}
  • useMemo와 마찬가지로, 함수, dependencies를 전달하며, 전달된 dependencies가 변하지 않는 한 이전에 생성한 함수가 재사용된다.

정리

Hook/기능 설명 사용 예시
React.memo() 컴포넌트의 불필요한 리렌더링 방지 (HOC) 동일한 props를 받으면 렌더링을 건너뜀
useMemo() 연산량이 많은 계산값을 메모이제이션 const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback() 이전 함수를 메모이제이션하여 불필요한 함수 재생성 방지 const onSave = useCallback(() => saveToServer(name, age), [name, age]);

3️⃣ React 컴포넌트의 생명주기

📌 생명주기

  • 생명주기란 컴포넌트가 생성되고 사용되고, 소멸될 때까지의 일련의 과정을 말한다.
  • 리액트의 생명주기는 클래스 컴포넌트에서만 사용할 수 있으며, 함수의 경우 리액트 훅인 useEffect를 통해 비슷한 작업을 수행할 수 있다.

마운트

  • 컴포넌트가 처음 실행될 때의 생성단계를 말한다.
  • 마운트 단계 메서드: constructor, getDerivedStateFromProps, render, componentDidMount

업데이트

  • 업데이트 단계 메서드: getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate

언마운트

  • 컴포넌트가 화면에서 제거되는 것을 의미한다.
  • 언마운트 단계 메서드: componentWillUnmount

정리

단계 설명 주요 메서드
마운트(Mount) 컴포넌트가 생성될 때 constructorgetDerivedStateFromPropsrendercomponentDidMount
업데이트(Update) state, props가 변경될 때 getDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate
언마운트(Unmount) 컴포넌트가 제거될 때 componentWillUnmount

Copy link

@BeanMouse BeanMouse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디자인이 너무 귀여운 것 같고 캘린더 직접 만든거 대단하세요 !!

제가 최적화 쪽으로 정말 문외한이라 이해해주시면 감사하겠습니당 🙇‍♀️

Comment on lines +70 to +72
{["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"].map((day) => (
<div key={day}>{day}</div>
))}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map 때문에 배열이 불필요하게 재생성 되는 것 같은데 forEach를 써보는건 어떨까요

Comment on lines 41 to 47
onKeyDown={(e) => {
if (e.key === "Enter" && inputValue.trim() !== "") {
addTodo(selectedDate, inputValue);
setInputValue(""); // 입력창 초기화
}
}}
/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수에서 리엑트 상태 관리 비동기성 때문에 이거 제대로 작동 안 할 가능성이 큽니당 (strict mode라 그럴 가능성이 더 큼!)
바로 e.currentTarget.value 써주는게 더 안정적일 거에요

그리고 keyDown은 isComposing 써줘서 한글 입력 오류 안 나도록 설정할 수 있습니당!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크롬에서 한글 입력 후 엔터 시 오류 납니다! 주희님 말대로 isComposing을 써서 해결할 수 있습니다!
image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 확인 못했던 부분이었는데 좋은 피드백 감사합니다!!

Comment on lines +66 to +74
.map((todo, index) => (
<TodoItem
key={`${selectedDate}-${index}`}
todo={todo}
index={index}
selectedDate={selectedDate}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마찬 가지로 map을 사용해서 배열을 한번 더 만들어줄 필요는 없을 것 같습니다

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

몰랏는데 map을 써야해요

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하핫

{index + 1}
</DateNumber>
<TodoPreview>
{uncompletedTodos.slice(0, 2).map((todo, i) => (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 분 코드 리뷰에서 봤는데 key값으로 index를 쓰는 게 안 좋다라고 하시더라 구용
사실 저도 index 썼습니다 하핫

링크 첨부 하고 갑니당~~
key에 index 넣으면 안되는 이유

import TodoList from "./TodoList";
import { ModalOverlay, ModalContent, CloseButton } from "../styles/StyledModal";

const Modal = ({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.Memo 빠진 것 같습니당

const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 +1
const day = String(today.getDate()).padStart(2, "0"); // 1자리 수일 경우 0 추가
return `${year}-${month}-${day}`; // "YYYY-MM-DD" 형식으로 반환

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구버전 IOS는 2025/08/07 이것만 알아 들었던걸 알고 계시나요...

제가 이거 파싱 문제 때문에 프로젝트에서 고생했었는데...
혹시 다른 곳에서 날짜 적용이 안된다하면 구버전 IOS를 사용하고 있어서 그런겁니다 하핫

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

몰랐던 사실이에요 /로 구분하는 게 더 안전한 방법이겠네요!! 좋은 정보 감사합니당

Copy link

@seoyeon5117 seoyeon5117 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다들 캘린더를 직접 구현하시는 것 같은데 다들 너무 대단한 것 같아요,, 리액트 많이 안다뤄보신걸로 아는데 잘하시네요... key 값을 index로 설정해서 key값 중복으로 로직이 꼬이셨다고 하는데 이런 이유 때문에 key값을 인덱스로 쓰는 것은 리액트 공식 문서에서도 권장되고 있지 않아요! id와 같이 고유한 값을 key값으로 주는게 좋습니다~ 2주차 과제하시느라 수고하셨습니다!

Comment on lines +89 to +97
{isModalOpen && (
<Modal
selectedDate={selectedDate}
closeModal={closeModal}
todos={todos}
addTodo={addTodo}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모달은 기술적으로 body 아래의 div로 출력되면 더 직관적이고 접근성이 더 좋기 때문에 Portal을 사용하는 게 좋습니다! 주희님 코드 참고하시면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안 그래도 주희님 코드리뷰하며 createPortal을 처음 알게 되었습니당... 적용해봐야겠어요!!

Comment on lines +74 to +76
const closeModal = () => {
setIsModalOpen(false);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 한줄짜리 코드는 따로 함수로 빼지 않고 바로 closeModal={() => setIsModalOpen(false)} 처럼 적어주는게 더 직관적일 것 같아요!

Comment on lines +33 to +47
export const CalendarDays = styled.div`
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
font-weight: bold;
color: #4caf50;
padding: 1rem;
`;

export const CalendarGrid = styled.div`
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.5rem;
padding: 1rem;
`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그리드 사용해서 캘린더 구현하셨네요👍

Comment on lines 41 to 47
onKeyDown={(e) => {
if (e.key === "Enter" && inputValue.trim() !== "") {
addTodo(selectedDate, inputValue);
setInputValue(""); // 입력창 초기화
}
}}
/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크롬에서 한글 입력 후 엔터 시 오류 납니다! 주희님 말대로 isComposing을 써서 해결할 수 있습니다!
image

background-color: #8bc34a;
color: white;
font-size: 1rem;
border: none;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버튼에서 border: none을 반복해서 사용하는 것 같은데 Global Style에서 이 부분을 리셋해줬으면 더 좋았을 것 같아요!

Comment on lines +4 to +12
import {
TodoContainer,
TodoHeader,
TodoInput,
AddButton,
TodoBoard,
SectionTitle,
TodoListWrapper,
} from "../styles/StyledTodoList";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 하나하나 import 하지 말고 import * as S from '../styles/StyledTodoList';처럼 사용하면 좋을 것 같아요! 철흥님이 이 방식으로 작성하신 것 같습니다~!

Comment on lines +49 to +51
onClick={() => {
addTodo(selectedDate, inputValue);
setInputValue(""); // 입력창 초기화

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로는 이렇게 두 줄 이상으로 코드가 늘어나면 함수로 따로 빼는게 더 좋다고 생각됩니다!

Comment on lines +1 to +6
@font-face {
font-family: "MyFont"; /* 원하는 폰트 이름 지정 */
src: url("./assets/fonts/MyCustomFont.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 GlobalStyles랑 index.css 모두에 font-face를 명시하신 이유가 따로 있을까요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

폰트 적용이 제대로 안 됐던 적이 있어 이리저리 추가하다가 빼는 걸 까먹었나 봅니다...😱

cursor: pointer;
position: relative;
outline: none;
transition: all 0.2s ease-in-out;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 애니메이션 구현 멋져요 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants