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주차] 이희원 미션 제출합니다. #8

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

Conversation

hiwon-lee
Copy link

@hiwon-lee hiwon-lee commented Sep 21, 2024

React-Todo : 배포링크

느낀점

이번 과제는 시작할 때 파일 구조를 잘 잡아보자는 생각이 있었다. 그래서 오히려 지난 과제보다 초반에 고민이 좀 많았다. 관련 자료 찾아보면서 이번 과제에 활용할만한 구조에 대해 고민하다가 우선은 가장 많이 쓴다고 하는 방식을 활용했다. 솔직한 말로 잘 쓴 건지는 모르겠지만, 구현하면서 기능 추가하거나 삭제할 때 지난 과제보다 편했다.
이번 key-question으로 메모이제이션 관련 함수에 조사하는 내용이 있어서 과제에 추가한 부분이 있다. 렌더링 최적화에 도움을 주는 함수라던데, 콘솔에 찍어보니 적용 유무 차이가 없어서 반영이 제대로 된 건지 모르겠다.
마지막으로 지난 코드 리뷰에서 내가 놓친 부분이나 개선하면 좋을 부분을 잘 얘기주셔서 도움이 아주 많이 됐다. 관련 내용들을 최대한 반영해보자는 마음으로 과제를 진행했다.
다른 분들 코드 리뷰 보는 게 생각보다 재밌어서 이번엔 나도 좀 (가능한..)꼼꼼하게 써볼까 싶다.

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

1. DOM, Virtual-DOM 차이

DOM(Document Object Model) : elements(Object)를 담는 Document의 트리 형태 구조 자체

  • DOM 조작 : api를 통해 구조에 접근, 변경 등의 작업을 하는 것

Virtual-DOM : 실제 DOM과 같은 내용을 담고 있는 복사본.

  • 복사본은 실제 돔이 아니나 자바스크립트 객체 형식으로 메모리에 저장되어 있다.
  • 브라우저에 직접 접근할 수 없다.

2. Virtual-DOM 사용 이유

결론 : 실제 DOM 조작의 시간을 단축한다.

문제 상황

블로그에서 게시물을 추가, 좋아요 달기 와 같은 조작을 할 때 돔의 내용을 수정해주어야 한다.
하지만 이러한 작업에서 모든 내용을 다시 그려주는(rerendering) 것은 비효율적이다.
(화면에 그려주는 작업은 비용이 많이 드는 작업으로 최적화해주면 좋다.)

해결 방법

문제를 해결하기 위해 Virtual-DOM을 사용한다.

(1) Diffing + Reconciliation

React는 항상 두 개의 Virtual-DOM을 유지한다.

  • 렌더링 이전의 정보
  • 렌더링 이후의 정보
    상태(state)가 변경될 때마다, React는 새로운 Virtual-DOM을 생성하여 렌더링한다. 이때, 새롭게 생성된 가상 DOM과 이전 가상 DOM을 비교해 변경된 부분을 찾아내는데, 이를 Diffing이라고 한다. 이 과정을 통해 변경된 부분을 빠르게 식별할 수 있으며, 그 부분만 실제 DOM에 적용한다. 이 과정은 Reconciliation이라고 한다.
(2) Batch Update

Reconciliation이 효율적인 이유는 React가 여러 업데이트를 하나로 묶어 처리하는 Batch Update 때문이다. 여러 변경 사항을 모아 한 번에 DOM에 적용함으로써 성능을 최적화할 수 있다.

결국 가장 많이 드는 화면 그리기를 최소화할 수 있다.

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

React에서 rerendering은 2가지 상황에서 이뤄진다.

  1. state의 변경
  2. props의 변경

rendering이 빈번하게 일어나는 것은 비용이 많이 드는 일이므로 최적화를 위해 React.memo(), useMemo(), useCallback()와 같음 함수를 사용한다.
앞선 3개의 함수을 메모이제이션 기법이라고 부르며, 이는 불필요한 rerendering을 방지하고 성능을 개선해준다.

  • React.memo() : porps가 변경되지 않으면 컴포넌트의 rerendering을 방지
  • useMemo() : 복잡한 값 계산을 메모이제이션하여 불필요한 계산을 방지합니다.
  • useCallback() : 함수를 메모이제이션하여 불필요한 함수 재생성과 리렌더링을 방지한다.

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

생명주기(Life Cycle)란 컴포넌트가 생성, 사용, 소멸되는 일련의 과정을 말한다.
모든 react컴포넌트는 Life Cycle Event가 있다.
Life Cycle Event : 생명주기 안 특정 시점에 자동으로 호출되는 메서드
Mount - Updaing - Unmount순으로 이뤄진다.

Mounting Event : React Element를 돔노드에 추가할 때 발생
  • constructor(): 컴포넌트가 처음 생성될 때 호출되며, 초기 state를 설정하거나 메서드를 바인딩하는 데 사용된다.
  • getDerivedStateFromProps(): 컴포넌트가 처음 생성되거나 props가 변경될 때, props로부터 state를 업데이트하고 싶을 때 사용된다.
  • render(): React 컴포넌트의 UI를 렌더링하는 함수다.
  • componentDidMount(): 컴포넌트가 처음으로 DOM에 추가된 직후에 호출된다. 여기서 api 호출과 같은 비동기 요청을 처리하거나, DOM에 직접 접근하는 코드를 작성할 수 있다.
Updating Event : state나 props가 변경되어 React Element를 갱신할 때 발생
  • getDerivedStateFromProps(): 새로운 props가 전달될 때 호출되며, state를 업데이트하기 위해 사용된다.
  • shouldComponentUpdate(): props나 state가 변경되었을 때 컴포넌트가 리렌더링할지 결정한다.
  • render(): 컴포넌트의 state나 props가 변경될 때 DOM을 갱신한다.
  • getSnapshotBeforeUpdate(): DOM이 갱신되기 전에 마지막으로 DOM의 상태를 가져올 수 있는 함수다. 스크롤 위치나 DOM 변경 전의 상태를 유지하기 위해 사용된다.
  • componentDidUpdate(): 컴포넌트가 업데이트된 후에 호출된다. 업데이트된 DOM에 대한 작업이나 비동기 작업 등을 처리할 수 있다.
Unmounting Event : React Element를 돔에서 제거할 때 발생
  • componentWillUnmount(): 컴포넌트가 DOM에서 제거되기 직전에 호출된다.

@@ -3,12 +3,15 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@react-icons/all-files": "^4.1.0",
Copy link

Choose a reason for hiding this comment

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

아이콘 사용 라이브러리를 사용하셨네요! 저는 svg 파일을 따로 다운 받아서 하나하나 스타일링 했는데 이런 간편한 방법이 있다는 걸 하나 배워갑니다!! 👍🏻👍🏻

Copy link

Choose a reason for hiding this comment

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

혹시 왜 모든 아이콘 파일을 한 번에 가져오는 라이브러리도 함께 사용하신 것일까요?-? 제가 몰랐던 라이브러리라 방금 찾아보니 @react-icons만으로도 각 아이콘 세트에서 필요한 것만 가져와서 사용할 수 있다고 나와있어 두 라이브러리 모두 설치하신 이유가 무엇인지 알고 싶습니다!!

Copy link
Author

Choose a reason for hiding this comment

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

음 사실 처음 라이브러리 설치할 때에 실수한 부분이었는데 그게 그대로 남아있게 된 것 같아요ㅠㅠ
사실 저도 두 개의 차이점을 명확하게 알지 못했는데 이번 기회에 다시 찾아보게 되었습니다. 감사합니다 ㅎㅎ

react-icons: 필요한 아이콘만 개별적으로 가져오는 방식, 더 가볍고 효율적인 사용이 가능.
@react-icons/all-files: 모든 아이콘 파일을 한 번에 로드, 더 많은 리소스를 사용하게 됨.

관련 사이트 : react-icons
설치 명령어 :

npm install react-icons --save

import Todo from './components/todo/Todo';
import { IoCheckmarkSharp } from 'react-icons/io5';

import './App.css';
Copy link

Choose a reason for hiding this comment

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

App 컴포넌트에서 styled-components가 아니라 App.css 파일을 따로 만든 이유가 따로 있으실까요?-? 전역 스타일 설정 때문이실까요?!

Copy link
Author

Choose a reason for hiding this comment

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

음.. 이건 reset.css를 활용하면서 비슷한 방식으로 적용하려고 했던 맥락이 제 머릿속에 있었던 것 같아요.
말씀하신 것처럼 전역으로 스타일을 줄 수 있다는 느낌..?을 주고싶었습니다.
근데 이 부분도 styled-components를 활용하면 좀 더 코드에 일관성이 생길것 같네요! 감사합니다~

background-color: #1b1a19;
font-size: 1.2rem;
gap: 0.5rem;
border-bottom: 1px solid var(--sub-color);
Copy link

Choose a reason for hiding this comment

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

var(변수명) 형식을 사용하셨네요! 저는 이것을 지난번 코드리뷰에서 알게 되었는데, 스타일 변경 등의 유지 보수에 있어 정말 유용한 개념인 것 같습니다 👍🏻👍🏻

@@ -0,0 +1,134 @@
/* http://meyerweb.com/eric/tools/css/reset/
Copy link

Choose a reason for hiding this comment

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

웹 브라우저의 기본 스타일링을 초기화해 일관된 스타일을 제공하기 위한 CSS 리셋 스타일시트를 사용하셨네요...!!!! 브라우저마다 기본적으로 제공하는 스타일이 달라 이를 통일하고 브라우저 차이로 인한 예기치 않은 디자인 문제를 방지해 원하는 디자인을 안정적으로 적용할 수 있겠네요!! 이런 개념을 희원 님의 코드 리뷰를 하며 얻어 갈 수 있어 기쁩니다... 🤩

@@ -0,0 +1,65 @@
import styled from 'styled-components';
import { DeleteButton, IsCompletedButton } from '../Button';
import { FaTrash } from 'react-icons/fa';
Copy link

Choose a reason for hiding this comment

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

아이콘을 사용함으로써 사용자가 버튼의 기능을 직관적으로 이해하는 데 큰 도움을 주는 것 같아요 👍🏻👍🏻


function Todo() {
const [todos, setTodos] = useLocalStorage('todos', []);
const memoizedTodos = useMemo(() => {
Copy link

Choose a reason for hiding this comment

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

렌더링 최적화 관련해서 useCallback, useMemo, React.memo 등을 적용한 후 콘솔에 확인해 봐도 잘 반영된 건지 모르겠다고 하신 게, 어떤 요소를 클릭했을 때 다른 요소들의 리렌더링 여부를 알 수 없었다는 말씀이실까요?-?
혹시 react developer tool을 사용하고 계신 게 아니라면 추천 드립니다! 이 tool을 이용하면 리렌더링 되는 부분이 네모 박스로 시각화 되어 보여져 최적화 반영 여부를 한 눈에 파악할 수 있습니다!

(만약 이런 뜻이 아니었다면 저를 정정해 주시길 바랍니다...😖 )

<h1>🐶CEOS 20기 프론트엔드 최고🐶</h1>
<nav>
<IoCheckmarkSharp />
TO DO
Copy link

Choose a reason for hiding this comment

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

이 nav 부분에 반응형 적용이 잘 안 되어 있는 것 같습니다. 🥲🥲 상대 단위를 사용하거나 화면이 작아질 때 flex 요소가 너무 좁아지지 않도록 flex-grow, flex-shrink 등을 조정해 사용해 보시면 어떨까요?!
더 나은 해결책을 발견하시면 공유 부탁 드립니다 🤩

image

Copy link
Author

@hiwon-lee hiwon-lee Sep 24, 2024

Choose a reason for hiding this comment

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

이 부분을 제가 확인해봤을 때는 nav부분에서 어떤 문제가 발생하는 지 명확히 모르겠어요ㅠㅠ
혹시 가장 오른쪽에 있는 하얀 막대같은 부분이 생기는 문제인가요?

image

이렇게 폭을 극한으로 줄였을 때 TO DO글자가 다음 줄로 줄바꿈하는 문제는 추가로 발견했습니다.

@@ -0,0 +1,46 @@
import styled from 'styled-components';

export function Button({ type, children, onClick }) {
Copy link

Choose a reason for hiding this comment

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

props로 {children}을 받아오셨네요! 이 특수한 props는 잘 사용을 안 했어서 몰랐는데 정말 유용한 것 같습니다. 해당 컴포넌트의 태그 사이에 넣은 내용이 모두 children으로 전달 되니 필요할 때 Button 태그를 사용한 후 버튼의 기능에 맞게 태그 안에 텍스트나 아이콘을 넣기만 하면 props로 전달 되어 렌더링 되니 버튼의 유연성이 증가하는 것 같아요! 이건 저도 꼭 사용해 봐야겠습니다 👍🏻👍🏻

Copy link
Author

Choose a reason for hiding this comment

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

이게 버튼에 있었군요..ㅋㅋ
발표때 왜 이걸 못찾았을까요......
민재님이 제가 얘기하고 싶었던 부분을 코드 리뷰로 다 짚어주셔서 급하게 설명할때 도움을 많이 받았답니다..ㅎㅎ

`;

// DeleteButton : 삭제 버튼
export const DeleteButton = styled(MainButton)`
Copy link

Choose a reason for hiding this comment

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

MainButton에서 기본 스타일을 상속 받아 추가 스타일을 적용하고 계시네요!! 코드의 중복을 줄이고 스타일을 일관 되게 유지하는 데 좋은 것 같습니다 👍🏻 저는 버튼을 사용하는 컴포넌트에서 각각 스타일링 해주었는데 희원 님의 방식을 배워야할 것 같네요! 🔥🔥

const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id);
Copy link

Choose a reason for hiding this comment

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

컴포넌트가 언마운트될 때 setInterval이 계속 실행되면 메모리 누수가 발생할 수 있음을 방지하기 위해 clearInterval을 호출하는 것은 좋은 습관인 것 같습니다!!!! 저도 시간을 업데이트하는 로직을 짤 때 참고해야겠어요 👍🏻🔥

Copy link
Author

Choose a reason for hiding this comment

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

민재님의 리액트 생애주기 발표를 듣고 저는 더 깊게 이해할 수 있었습니다 ㅎㅎㅎㅎㅎ

const completedCount = todos.filter((todo) => todo.isCompleted).length;

// 진행률 계산 (완료된 할 일의 수 / 전체 할 일 수)
const progress = todos.length > 0 ? (completedCount / totalTodo) * 100 : 0;
Copy link

Choose a reason for hiding this comment

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

언뜻 보면 사소하다고 생각하실 수 있겠지만, 이렇게 totalTodo 개수가 0일 경우, 즉 나누는 수가 0일 경우를 고려하는 부분이 코드의 안정성에 기여하는 부분이라 중요하다고 생각됩니다! 저는 이런 부분을 쉽게 간과하는 타입이라 반성하게 되네요 ㅠ..ㅠ
const progress = completedCount / totalTodo) * 100 // 이렇게 삼항연산자를 쓰지 않고 바로 대입...

}

progress {
width: 300px;
Copy link

Choose a reason for hiding this comment

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

width를 고정 값이 아닌 비율로 나타낸 후 반응형을 위해 max-width 속성을 이용해 보시는 게 어떨까요?!?
저는 일단 고정 단위의 사용을 피하고 반응형을 위해 width를 비율로 설정하고 있는데 이는 정답이 아닐 수 있으니 더 좋은 방법을 찾으시거나 알고 계시는 게 있다면 공유 부탁드립니다 🔥👍🏻

</div>
<div className="progressInfo">
<label htmlFor="todoProgress">진행률: {Math.round(progress)}%</label>
<progress
Copy link

Choose a reason for hiding this comment

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

저는 이 todolist 완료 상태를 진행률로 나타낼 생각을 못했어서... 🥲🥲 너무 좋은 아이디어인 것 같습니다!! (코드 리뷰가 끝나면 제 코드를 리팩토링 해 볼 생각입니다🤩)

htmlFor에 대해 검색해 보니, htmlFor을 이용해 label 태그가 id가 todoProgress인 progress 태그를 참조하게 하여 label 요소를 클릭했을 때 해당 progress 요소에 포커스가 가거나 그 요소와 상호작용하게 된다고 나와있는데 희원 님의 배포 화면에서 label을 클릭했을 때는 포커스가 맞춰지지 않아서요!
이게 포커스가 맞춰지는 것은 기본 동작이 아닌 선택 요건인 것일까요...? 포커스 보다는 '상호작용, 연결' 된다는 것에 초점을 맞춰야 할까요?!

@@ -0,0 +1,29 @@
import { useState } from 'react';

// useLocalStorage : localStorage 관리하는 커스텀 훅
Copy link

Choose a reason for hiding this comment

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

LocalStorage 관리 로직을 커스텀 훅을 사용해 여러 컴포넌트에서 동일한 로직을 쉽게 재사용할 수 있게 하셨군요!!!!
저는 이 생각을 못하고 App 컴포넌트에 로직을 구현하고 그대로 사용했어서 너무 코드의 가독성이 떨어졌는데 커스텀 훅을 사용하는 것으로 리팩토링 해 보아야겠습니다 👍🏻👍🏻

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.

안녕하세요. 희원님! 멋진 과제 잘보았습니다. 기존 UI에 더해서 멋진 기능들을 추가해주셨네요. 👍👍👍 코드보면서 몇가지 사소하게 발견한것들 적어 봤습니다! 고생하셨어요!!!

function App() {
return (
<div className="App">
<h1>🐶CEOS 20기 프론트엔드 최고🐶</h1>
<nav>
<IoCheckmarkSharp />
Copy link
Member

Choose a reason for hiding this comment

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

이부분을 임포트하는 이유가 있을까요?! 저는 이 라이브러리 사용할때 컴포넌트에서 바로 해당 아이콘을 임포트해서 썻거든요!

Copy link
Author

Choose a reason for hiding this comment

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

아이콘 말씀하시는거죠?! 음 그건 nav 컴퍼넌트를 만들지않아서 일단 그렇게 넣었던 것 같습니다..ㅠㅠ
말씀 들어보니 따로 컴포넌트를 두고 해당 아이콘을 바로 임포트하는게 나을것같습니다. 감사합니다ㅎㅎ

Copy link
Member

Choose a reason for hiding this comment

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

Todo 컴포넌트에서 todos를 관리해서 props로 전달해주는 아이디어가 정말 좋은 것 같아요!! 👏

Comment on lines +33 to +38
<Button
type="submit"
onClick={handleSubmit}
>
추가
</Button>
Copy link
Member

Choose a reason for hiding this comment

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

handleSubmit 함수에서 버튼의 onClick 핸들러 중복됩니다!

Form에서는 아래와 같이 작성해도 정상 작동해요

Suggested change
<Button
type="submit"
onClick={handleSubmit}
>
추가
</Button>
<Button type="submit">
추가
</Button>

Copy link
Author

Choose a reason for hiding this comment

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

오 감사합니다!


const handleSubmit = (e) => {
e.preventDefault();
console.log(inputValue);
Copy link
Member

Choose a reason for hiding this comment

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

사소하지만 디버깅을 위해 사용한 console.log를 마지막에 제거하는 것도 습관들여봐요 :)_

border: 0.5rem 1rem;
margin-top: 2rem;
display: flex;
flex-wrap: wrap;
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/todo/TodoList.js Outdated Show resolved Hide resolved
Comment on lines +11 to +13
const memoizedTodos = useMemo(() => {
return todos;
}, [todos]);
Copy link
Member

Choose a reason for hiding this comment

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

todos는 상태 값인 것 같은데, 변경 시 매번 업데이트 되어서, useMemo로 todos를 메모이제이션하는 것은 불필요한 것 같아요!

코드를 제거하고 todos를 직접 사용하셔도 무방할 것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

아 역시 제가 잘못 사용하고 있었군요. 피드백 감사합니다ㅠㅜ

gap: 1rem;
`;

export default React.memo(Todo);
Copy link
Member

Choose a reason for hiding this comment

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

Todo 컴포넌트는 상태를 관리하고 있으며, 상태가 변경될 때마다 다시 렌더링되어지는 것 같아요. 그런데 React.memo는 props가 변경되지 않을 때 렌더링을 방지하기 위해 사용되는데, 이 경우에 적합하지 않아서 삭제해도 좋을 것 같습니다~:)

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