Skip to content
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

[2주차] 이가빈 미션 제출합니다. #4

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

billy0904
Copy link

배우고 느낀 점

  • 지난 바닐라JS 과제의 App.js 파일을 여러 컴포넌트로 분리해서 구조화시키면서 React의 컴포넌트 기반 구조를 조금 더 자세히 이해할 수 있었던 것 같습니다. 구현한 기능이 복잡하지 않아서 재사용하는 컴포넌트가 많지는 않았지만 앞으로의 유연한 코드 작성에 많은 도움이 될 것 같습니다. 그리고 styled-components를 사용하면서 코드 작성 측면에서나 가독성 측면에서 고유한 css 클래스를 생성하여 사용하는 CSS-in-JS의 장점을 다시 한 번 느낄 수 있었습니다.

많은 시간을 투자한 부분

  • 달력 기능을 추가적으로 구현하면서 react-calendar 라이브러리를 사용하였는데 날짜를 클릭했을 때 해당 날짜의 투두 리스트가 렌더링되는 흐름을 구현하면서 상태 관리와 라이브러리 통합의 중요성을 알게 되었습니다. 해당 기능을 구현하는데에 예상보다 많은 시간을 투자하게 되어서 렌더링 최적화에는 상대적으로 신경을 덜 쓰게 되었던 것 같아서 아쉬움이 있습니다. 궁금하신 점은 언제든지 질문 남겨주시고 부족함이 있는 부분에는 아낌없는 피드백 부탁드립니다!🤗

배포 링크

https://react-todo-20th-gamma.vercel.app/

Key Question

1. Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?

Virtual-DOM

메모리 내에 가상으로 존재하는 DOM

1

  • Virtual DOM(가상 돔)은 실제 DOM을 추상화한 개념
  • 속도 문제나 과부하로 인한 버그를 개선하고자 React의 Virtual DOM이 등장하였다.
  • 렌더링 과정을 거치지 않은 가상의 DOM을 이용해 DOM의 상태를 메모리에 저장한 후 실제 DOM을 수정하기 전에 Virtual DOM에서 먼저 변경사항을 처리한다.

왜 Virtual-DOM을 사용하는가?

  • 변경 사항이 있을 때마다 실제 DOM을 조작하는 방식이 아닌, 메모리에 존재하는 가상의 DOM을 변경하고 가상 DOM의 이전 상태와 비교하여 실제로 변경이 일어난 부분, 즉 최소한의 내용만 실제 DOM에 업데이트하여 효율적인 렌더링이 가능하다.

✅비교(Diffing)

  • Virtual DOM은 이전 상태와 새로운 상태를 비교(Diffing)하여 변경된 사항을 찾음

✅패치(Patching)

  • 변경된 부분만 실제 DOM에 업데이트하여 렌더링 비용 최소화

2. React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.

렌더링(rendering)

화면에 특정한 요소를 그려내는 것

  • 브라우저의 렌더링 - DOM요소를 계산하고 그려내는 것
  • DOM과 CSSOM이 결합하여 브라우저에 요소들을 그리고, JS를 통해 브라우저에서 제공하는 DOM API를 호출하면서 브라우저에 렌더링된 화면을 변화시킨다.

컴포넌트의 리렌더링 조건

💡부모로부터 전달받은 props가 변경될 때

💡부모 컴포넌트가 리렌더링 될 때

💡자신의 state가 변경 될 때

메모이제이션(Memoization)

함수 호출의 결과를 캐싱하고 동일한 입력이 다시 발생할 때 캐싱된 결과를 반환하는 프로그래밍 기술

  • 동일한 입력으로 여러 번 호출되는 함수 또는 컴포넌트가 있을 때 React에서 유용하게 사용된다.
  • 메모이제이션를 사용하면 동일한 결과를 불필요하게 다시 계산하지 않고, 캐시된 결과를 반환할 수 있다.
  • useCallback, useMemo와 같은 메모이제이션 훅을 통해 성능을 향상시키고 코드의 복잡성을 줄일 수 있다.

React.memo()

기존의 컴포넌트의 UI를 재사용할 지 판단

  • HOC(Higher Order Component)
  • 리액트는 state가 변할 경우 해당 컴포넌트와 하위의 컴포넌트들을 모두 리렌더링 한다.
  • 컴포넌트가 동일한 props로 동일한 결과를 렌더링한다면, React.memo를 호출하고 결과를 메모이징(Memoizing)하도록 할 수 있다.
  • 즉, React는 컴포넌트를 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용한다.
  • 성능 최적화만을 위하여 사용되며 버그를 발생시킬 수 있으므로 렌더링을 방지하기 위해서는 사용하지 않는 것이 좋다.

useMemo()

값을 메모이제이션 할 수 있도록 해주는 함수

useMemo(() => fn, [deps])
  • useMemo는 두 가지 인자를 받는다.
  • 첫번째 인자로 콜백함수를 받아 리턴값을 메모하며 두번째 인자로 의존성 배열을 받는다.
  • 의존성 배열 안에 있는 값이 업데이트 될 때에만 콜백 함수를 호출하여 메모리에 저장된 값을 업데이트한다.
  • 빈 배열을 넣을 경우 useEffect와 마찬가지로 마운트 될 때에만 값을 계산하고 그 이후로는 메모이제이션된 값을 가져와 사용한다.
  • 만약 새로운 값을 만들어서 사용해야 하는 상황임에도 이전의 결과를 그대로 활용하게 되면 버그가 발생할 수 있다.

useCallback()

메모이제이션된 콜백 함수를 반환

useCallback(fn, [deps])
  • 즉, 이미 생성된 함수를 반환한다.
  • 메모이제이션된 함수는 콜백 함수의 의존성이 변경되었을 때에만 변경된다.
  • 불필요한 렌더링을 방지하기 위해 참조의 동일성을 보장하거나, 자식 컴포넌트에 의존적인 콜백 함수를 전달할 때 유용하다.
  • useMemo와 마찬가지로 useCallback 의존성 배열에 있는 상태나 props가 변경되지 않는다면 해당 함수는 다시 생성되지 않는다.

3. React 컴포넌트 생명주기에 대해서 설명해주세요.

React Component Life Cycle

생명주기: 컴포넌트가 생성 / 사용 / 소멸될 때까지 일련의 과정

💡생명주기는 클래스 컴포넌트에서만 사용할 수 있으며 **함수형 컴포넌트**에서는 
생명주기 메서드 대신 useEffect()를 사용해 마운트, 업데이트, 언마운트 시 필요한 동작을 정의할 수 있다.
  • 생명주기는 크게 마운트(Mounting), 업데이트(Updating), 언마운트(Unmounting) 단계로 나뉜다.
    2

마운트(Mounting)

컴포넌트가 처음 DOM에 추가되는 단계

  • 아래 메서드들이 순차적으로 호출된다.

1. constructor()

컴포넌트를 새로 만들 때 호출되는 React 컴포넌트 생성자 함수

  • 컴포넌트를 상속하는 컴포넌트의 생성자 함수를 구현할 때는 다른 구문에 앞서 super(props)를 호출해야 한다.
  • this.state에 객체를 할당하여 로컬 state를 초기화할 때 or 인스턴스에 이벤트 핸들링 메소드를 바인딩할 때 사용된다.

2. static getDerivedStateFromProps()

props로 받아온 값을 state에 반영할 때 사용

  • 시간의 흐름에 따라 변하는 props에 state가 의존할 경우에 사용하지만 용례가 드물다.

3. render()

컴포넌트를 렌더링

  • this.props와 this.state 값을 활용하여 요소를 반환한다.
  • 클래스형 컴포넌트에서 반드시 구현되어야 하는 유일한 메서드이다.

4. componentDidMount()

컴포넌트가 마운트된 직후 호출

  • 컴포넌트가 필요로하는 데이터를 얻기 위해 AJAX 요청을 하거나, DOM을 조작하는 작업을 수행한다.

업데이트(Updating)

props나 state가 변경되어 컴포넌트가 다시 렌더링되는 단계

  • 아래 메서드들이 순차적으로 호출된다.

1. static getDerivedStateFromProps()

2. shouldComponentUpdate()

리렌더링 여부 결정

  • true 리턴시 리렌더링, false 리턴 시 리렌더링X

3. render()

4. getSnapshotBeforeUpdate()

컴포넌트의 변경사항이 DOM에 반영되기 전 DOM 상태를 얻는 메서드

  • 스냅샷 값이나 null을 반환한다.

5. componentDidUpdate()

리렌더링을 마치고, 화면에 변경사항이 반영되고 난 뒤에 호출

  • 컴포넌트가 업데이트된 후 DOM을 조작하거나 AJAX 요청을 보낼 수 있다.

언마운트(Unmounting)

컴포넌트가 DOM에서 제거되는 단계

  • 아래 메서드가 호출된다.

componentWillUnmount()

컴포넌트가 마운트 해제되어 화면에서 제거되기 직전에 호출

  • 타이머 제거, 네트워크 요청 취소, componentDidMount()에서 생성한 구독 해제 등 필요한 모든 정리 작업을 수행한다.

Copy link

@yyj0917 yyj0917 left a comment

Choose a reason for hiding this comment

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

직관적인 UI가 인상깊은 것 같습니다! 코드 구조가 저랑 유사해서 더 재밌게 코드리뷰 할 수 있었던 것 같아요. 과제하시느라 수고많으셨습니다! 많이 배워가겠습니다!! 👍 😁

justify-content: center;
&:hover {
border-radius: 15%;
border: 1px solid #91d1ff;
Copy link

Choose a reason for hiding this comment

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

hover했을 때 border의 크기가 추가되어 캘린더에서 약간의 움직임이 있는 것 같습니다! border의 활용도 좋다고 생각되지만, 스타일이 변하지 않는 걸 신경쓸 때는 box-shadow: inset을 활용하는 방안도 괜찮았던 것 같아요!

Comment on lines +13 to +20
useEffect(() => {
loadTodoList(currentDate);
}, [currentDate]);

const loadTodoList = (date) => {
const todos = getTodoList(date);
setTodoList(todos);
};
Copy link

Choose a reason for hiding this comment

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

함수의 선언 위치관련해서 기존 자바스크립트에서는 화살표 함수로 선언된 함수가 호출된 곳보다 아래에서 선언되었을 때 호이스팅 문제로 에러가 발생할 가능성이 있습니다. 다만 useEffect 특성상 렌더링 이후에 useEffect가 실행되기 때문에 이미 모든 함수가 렌더링된 후에 useEffect 안에서 함수가 호출되어 에러가 발생하지 않습니다. 코드상 에러가 발생하지는 않으나 호출 전에 선언하는 것도 코드 가독성같은 측면에서 좋을 수 있을 것 같습니다!

저도 습관을 들이는 중입니다!

<TodoList>
{todoList.map((todo, index) => (
<TodoItem
key={index}
Copy link

Choose a reason for hiding this comment

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

index도 좋지만, todo에 고유한 id값을 넣어서 관리한다면 여러 방면으로 활용할 수 있는 범위가 넓어질 수 있을 것 같아요!

Comment on lines +2 to +7
export const formatDate = (date) => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString();
const day = date.getDate().toString();
return `${year}년 ${month}월 ${day}일`;
};
Copy link

Choose a reason for hiding this comment

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

날짜 포맷 맞춰주는 유틸함수 사용한 거 재사용성이 정말 좋은 것 같습니다!

padding-right: 20px;

// 스크롤바 스타일링
scrollbar-width: thin;
Copy link

Choose a reason for hiding this comment

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

오 스크롤바를 스타일링 할 수 있군요! 나중에 써먹겠습니당

Copy link
Member

Choose a reason for hiding this comment

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

오 저도 처음 알았네요...!

}
`;

const StyledCalendar = styled(BasicCalendar)`
Copy link

Choose a reason for hiding this comment

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

styled에 BasicCalendar가 있는 것 배워가겠습니다!

Copy link

Choose a reason for hiding this comment

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

오 저도 BasicCalendar 배워갑니다👍🏻👍🏻

Comment on lines +20 to +27
const Container = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
min-width: 800px;
max-width: 1000px;
margin: 0 auto;
`
Copy link

Choose a reason for hiding this comment

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

가로 세로로 overflow가 조금 있는 것 같습니다! 여러 부분에서 미세하게 달라진 스타일들이 영향을 준 것 같아서 overflow가 사라진다면 더 깔끔한 투두리스트가 될 것 같아요!

Comment on lines +54 to +57
const moveDate = (days) => {
const newDate = new Date(currentDate);
newDate.setDate(newDate.getDate() + days);
setCurrentDate(newDate);
Copy link

Choose a reason for hiding this comment

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

const newDate = addDays(currentDate, days);
라는 유틸함수도 있는 것 같아요! Date 메서드들 중에서 유용한 게 많은 것 같아서 같이 활용해보면 좋을 것 같습니다!

Copy link
Member

@ddhelop ddhelop left a comment

Choose a reason for hiding this comment

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

안녕하세요. 가빈님! 🙂🙂
멋진 과제 하시느라 너무 고생하셨습니다! 캘린더를 이용해서 투두리스트를 설정할 수 있게 만드신게 너무 인상적이였어요... 👏👏👏 함수도 잘 구성하고, 열심히 하신 흔적이 보였어요.. 고생하셨습니다!!

? todoList.filter((todo) => todo !== changedTodo)
// 완료 토글 처리
: todoList.map((todo) =>
todo === changedTodo ? { ...todo, completed: !todo.completed } : todo
Copy link
Member

Choose a reason for hiding this comment

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

객체를 비교할 때 참조 동등성(===)을 사용하면 예상치 못한 결과가 발생할 수 있는데, 특히 데이터가 로컬 스토리지에서 불러와지는 경우 객체의 참조가 변경될 수 있다고 합니다.

id를 이용해서 고유한 값으로 객체를 비교하는 것도 예상치 못한 오류를 방지하는 방법일 것 같습니다!

padding-right: 20px;

// 스크롤바 스타일링
scrollbar-width: thin;
Copy link
Member

Choose a reason for hiding this comment

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

오 저도 처음 알았네요...!

src/components/TodoItem.jsx Show resolved Hide resolved

export default TodoItem;

const TodoItemContainer = styled.li`
Copy link
Member

Choose a reason for hiding this comment

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

만약 TodoItem이 ul 또는 ol 내부에서 사용되지 않는다면, 시맨틱을 위해 div로 변경하면 좋을 것 같아요

Comment on lines +9 to +17
// 투두 완료 토글
const toggleTodoComplete = () => {
onTodoChange(date, todo, false);
};

// 투두 삭제
const deleteTodo = () => {
onTodoChange(date, todo, true);
};
Copy link
Member

Choose a reason for hiding this comment

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

이 두함수는 공통요소가 2개이고, boolean값만 다른데, 하나의 함수로 통일해서 리팩토링하면 좋을 것 같아요

Suggested change
// 투두 완료 토글
const toggleTodoComplete = () => {
onTodoChange(date, todo, false);
};
// 투두 삭제
const deleteTodo = () => {
onTodoChange(date, todo, true);
};
const handleAction = (action) => {
onTodoChange({ date, todo, action });
};

Copy link

@ryu-won ryu-won left a comment

Choose a reason for hiding this comment

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

코드리뷰 맡게된 김류웝입니다~!
전반적으로 ui가 너무 이뻤고, 캘린더 구현하신 부분 너무 유용할 거 같아요~!
코드리뷰를 다 하지 못해서 오늘 스터디 끝나고 추가적으로 하겠습니다!💗

}
`;

const StyledCalendar = styled(BasicCalendar)`
Copy link

Choose a reason for hiding this comment

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

오 저도 BasicCalendar 배워갑니다👍🏻👍🏻

Comment on lines +22 to +27
// 남은 할 일 개수
const updateLeftNum = () => {
const leftNum = todoList.filter((todo) => !todo.completed).length;
return `할 일 ${leftNum}개`;
};

Copy link

Choose a reason for hiding this comment

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

const leftTodoText = useMemo(() => {
    const leftNum = todoList.filter((todo) => !todo.completed).length;
    return `할 일 ${leftNum}개`;
}, [todoList]);

useMemo를 사용해 todoList가 변경될 때만 계산하도록 하는 방법도 좋을 거 같습니다~!

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.

5 participants