-
Notifications
You must be signed in to change notification settings - Fork 75
[1팀 김세준] Chapter 1-3 React, Beyond the Basics #75
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
base: main
Are you sure you want to change the base?
Conversation
|
와 장난 아니게 멋져요 대장! |
ywkim95
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세준님 고생 많으셨습니다!
파일 단위로 분리하시고 구현하시는 데 고민ㅇ르 많이 하신 것 같아요!
이후에는 일관성이나 단일 책임 원칙 등에 대해서 고민 해보시면 좋을 듯 합니다!
이번 주도 고생 많으셨고, 다움 주도 화이팅입니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
env로 관리하는 건 좋은 방식이라고 생각이 드네요! 다른 부분만 따로 분리되서 접근하는게 좋은 생각이라고 생각됩니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Providers를 통해서 Provider들만 따로 분리하는 것도 좋은 접근 방식 같아요!
| } | ||
| }; | ||
|
|
||
| return MemoizedComponent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MemoizedComponent 이외에 사용하는 함수나 값이 없다면 즉시 반환을 하셔도 괜찮을 듯 합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shallowEquals에서 undefined나 null에 대한 체크를 명시적으로 해보시는 건 어떠세요?
| * - 초기 랜더링 시에는 인자 없이 calculateValue를 호출한 값 반환 | ||
| */ | ||
| const ref = useRef({ | ||
| value: undefined as T, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined as T로 선언하신 이유가 어떤걸까요? 궁금합니다
| export function useRef<T>(initialValue: T): { current: T } { | ||
| // React의 useState를 이용해서 만들어보세요. | ||
| return { current: initialValue }; | ||
| const [ref] = useState({ current: initialValue }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분에서 일반적으로 객체나 값을 직접적으로 할당하는데 큰 데이터나 복잡한 연산에서는 조금 비효율적인 것으로 알고 있어요!
아래와 같이 선언하시면 최적화와 성능을 같이 잡을 수 있을 거에요!
const [refValue] = useState(() => ({ current: initialValue });There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI와 로직을 분리해보시는 건 어떠세요?
더 깔끔한 코드가 나올 듯 합니다!
| notification.type === "success" | ||
| ? "bg-green-500" | ||
| : notification.type === "error" | ||
| ? "bg-red-500" | ||
| : notification.type === "warning" | ||
| ? "bg-yellow-500" | ||
| : "bg-blue-500" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분을 함수로 분리해서 사용하시면 어떨까요? 가독성이 더 개선될 것 같아요!
과제 체크포인트
배포 링크
https://kk3june.github.io/front_5th_chapter1-3/
기본과제
심화 과제
과제 셀프회고
기술적 성장
좀 더 깊이 있게 문제에 대해 고민하기 시작했습니다. ‘과제를 해결’ 하기 위해 문제 해결에 매몰되지 않고, 항해를 원래 시작했던 목표에 맞춰 ‘올바른 학습 방법을 습득’ 하는데 우선 순위를 두고 이번 주차에 임했습니다. 1주차, 2주차 BP 들을 보면서 같은 문제를 다른 관점에서 바라보고 표면적인 문제 이면에 숨어있는 원리에 대해 고민하는, 사고하는 방법을 습득하는데 중점을 두었습니다. 덕분에 이전이라면 지나쳤을 내용에 대해 고민해보는 시간을 가질 수 있었습니다.
심화과제 중 첫번째 테스트 케이스가 ‘초기 렌더링 시 모든 컴포넌트가 한 번씩 렌더링되어야 한다’ 였는데, 계속 랜더링이 한번 더 발생하는 이슈가 있었습니다.
기본 과제를 다 통과 했다는 이유로 구현된 hook 에 대해 살펴볼 생각을 하지 못하고 전역에서의 context 상태 변화에 따라 발생하는 이슈로 짐작하여 context 파일의 구조를 바꿔보기도 하고 context 호출 시점을 바꿔보기도 하다가 도저히 문제가 해결 되지 않았습니다.
부끄럽지만 다른 분들의 PR 에서 테스트 케이스를 모두 통과한 컴포넌트를 복붙하여 테스트를 돌려 보았는데, 역시나 동작하지 않았고 그때서야 기본 과제를 하며 구현했던 기능에 뭔가 문제가 있음을 발견했습니다.
😥 before
DOM을 비교하여 변경사항이 있을 때만 컴포넌트를 재생성 하여 Reconcilation이 발생하도록 하고, 그렇지 않은 경우 이전의 변경되지 않은 컴포넌트를 전달해야 하는데, 비교 결과와 무관하게 컴포넌트가 기본적으로 재생성 되도록 하여 매번 리렌더링이 발생했던 것이었습니다.
✅ after
기본적으로는 이전에 저장된 요소를 전달함으로써 문제를 해결하였습니다.
기능에 대해 제대로 이해하지 않고 문제해결에만 급급하다 보니 에러의 발생 원인조차 자력으로 찾을 수 없었습니다. 이를 반면교사 삼아 사용하는 기능에 대해 이해를 하고 사용할 수 있도록 노력해야겠다는 다짐을 한번 더 하였습니다.
코드 품질 개선
context 호출 위치에 대한 고민
ItemList 컴포넌트는 onClick 이벤트가 발생하면 데이터를 1000개씩 추가하여 하위 요소를 렌더링 하고 있습니다. 이때 theme 상태에 따라 스타일링이 달라지는데, 이를 위해 아래 2가지 방식으로 컴포넌트를 구현할 수 있었습니다.
첫 번째 방식은 상위 컴포넌트에서 한번만 context를 호출하고 하위 컴포넌트에 context 를 전달하는 방식이고, 두 번째 방식은 렌더링 되는 하위컴포넌트에서 개별적으로 context를 호출하는 방식입니다. 첫번째 방식은 context를 한번만 호출하지만 context 값이 변경될 경우 하위 컴포넌트가 모두 렌더링 됩니다. 반면 두번째 방식은 context를 하위 컴포넌트 갯수만큼 호출하지만 context 값이 변경 되었을 때 해당 컴포넌트만 렌더링 되게 됩니다.
테스트 가설
context를 전역상태에 접근하는 비용이 props 로 상태 값을 전달하는 것보다 비용이 클 것으로 짐작되기에, 첫번째 방식이 성능적으로 우위를 보일 것이다.
이를 비교하기 위해 Profiler를 사용하여 2가지 방식을 테스트를 진행하였습니다.
(ItemList에서 Context 소비)
(Item에서 Context 소비)
Anonymous 컴포넌트 시간 비교
(ItemList에서 Context 소비)
(Item에서 Context 소비)
MemoizedComponent 시간 비교
(ItemList에서 Context 소비)
(Item에서 Context 소비)
예외 케이스가 존재하기는 했지만, 대체적으로 상위 컴포넌트에서 한번만 context 를 호출하고 하위 컴포넌트에 해당 context를 전달하는 방식이 성능적으로 10% 우위를 보였습니다.
테스트 결과에 따라 첫 번째 방식인 상위 컴포넌트에서 context를 선언하고 하위 컴포넌트에 props 로 전달하는 방식으로 구현하였습니다.
테스트 결과에 대해 Claude Sonet 3.7 로 분석을 요청한 바에 따르면 두 번째 방식은 3가지 단점이 존재하였습니다.
결국 구독 비용 뿐만 아니라, 구독 상태를 처리하는데도 하위 요소 갯수만큼 추가비용이 발생할 수 있음을 확인하였습니다.
결론
특정 상태에 따라 다수의 컴포넌트가 리렌더링 되어야 한다면, 해당 컴포넌트들을 감싸고 있는 wrapper 컴포넌트에서 context를 호출하고 하위 컴포넌트에서 context를 pros로 전달받되, 하위 컴포넌트들은 memo를 사용하여 props가 변경될 때 리렌더링 되도록 하는게 가장 좋은 방법 인 것 같습니다.
다만 context 호출을 할때는 해당 context 값에 영향을 받을 요소들을 잘 타겟팅 하여 wrapping 하여야 할 것 같습니다.
context 파일 분리
context 와 관련된 코드들을 아래와 같이 한 파일로 관리했습니다.
😥 before
코드가 정상 동작하였지만 아래와 같은 warning 이 발생하였습니다.
React 에서는 코드 변경 시 페이지 전체를 새로고침 하지 않고, 수정된 컴포넌트만 빠르게 업데이트 하는 Hot Reloading 기능을 제공합니다.
이 기능은 파일이 React 컴포넌트만을 내보내는 경우 정상 동작을 하고, React 컴포넌트 이외의 것들을 함께 내보내고 있다면 제대로 동작하지 않을 수 있습니다.
Fast Refresh 는 파일의 React 컴포넌트를 추적하여 코드 변경 시 해당 컴포넌트를 업데이트 하게 되는데, React 컴포넌트 외 다른 요소가 섞여 있는 경우 파일에서 무엇을 업데이트 해야할지 모호해지기 때문입니다.
따라서 아래와 같이 코드를 파일 단위로 분리하였습니다.
✅ after
학습 효과 분석
React Fiber
React Fiber는 동기적으로 렌더링을 처리하던 Stack Reconciler을 개선하기 위해 React16(2017)에서 도입된 새로운 렌더링 로직입니다.
React Fiber 도입을 통해 React는 비동기 렌더링, 우선순위 기반 작업 스케줄링, 그리고 복잡한 UI 애플리케이션의 효율적인 업데이트를 제공하고 있습니다.
Fiber의 주요 개념
Fiber는 화면을 렌더링 하면서 수시로 콜 스택의 작업들을 중단하고 태스크 큐의 작업을 진행합니다. 따라서 사용자 행동에 따른 I/O 입력, 이벤트들이 UI 렌더링보다 우선순위를 갖고 작업이 수행 됩니다.
리뷰 받고 싶은 내용
context 와 관련하여 테스트를 진행한 부분이, 테스트 목적과 부합하는 적절한 방법일까요?
context 구독 비용과 props로 전역 상태를 전달하는 비용을 정확히 비교하기 위해서는 어떻게 테스트를 진행 해야할까요? 현재 테스트는 렌더링 시간만 측정했지만, Context 구독과 Props 전달의 메모리 사용량이나 CPU 부하 같은 다른 측면도 고려해야 할까요?
Fiber의 동작 원리를 더 이해하려면 React 소스 코드를 분석하거나 특정 아티클을 참고하는 것이 일반적인가요? 추천할 만한 자료나 학습 경로가 있을까요?
현재 Providers 라는 파일에서 모든 context Provider를 한번에 import 하여 App.tsx에서 일괄 적용 하고 있습니다. 특정 context 가 일부 컴포넌트에서 사용된다면 해당 context의 Provider 는 영향을 주는 컴포넌트를 wrapping 하여 개별적으로 사용해야될 것 같습니다.