-
Notifications
You must be signed in to change notification settings - Fork 75
[1팀 김효정] Chapter 1-3 React, Beyond the Basics #67
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.
효정님 고생 많으셨습니다!
코드를 구현하는 과정에서 여러가지 시도를 하신게 보입니다!
이후에는 코드의 일관성이나 단일 책임 원칙 등을 고려하셔서 구현하시면 더 좋은 코드가 나올 듯합니다!
이번 주도 고생 많으셨고, 다음 주도 화이팅입니다!
| return objA.every((x, i) => deepEquals(x, objB[i])); | ||
| } | ||
|
|
||
| const keys = new Set([...Object.keys(objA), ...Object.keys(objB)]); |
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.
Array가 아니고 Object인 경우에서도 objA와 objB의 키의 갯수를 비교하여 즉시 반환을 추가로 구현하셔도 좋았을 듯합니다!
Set으로 구현하신 이유가 있을까요? Set이 아니라 키의 갯수를 비교 후 같으면 하나의 Object.keys 로 루프를 돌리는 방식은 어떻게 생각하시나요?
|
|
||
| const keys = new Set([...Object.keys(objA), ...Object.keys(objB)]); | ||
|
|
||
| for (const key of keys) { |
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.
every로 구현하시는 것에 대해서도 고려해보시면 좋을 듯 합니다!
| const isSame = prevPropsRef.current && _equals(prevPropsRef.current, props); | ||
|
|
||
| if (isSame && renderedRef.current) { | ||
| return renderedRef.current; | ||
| } | ||
|
|
||
| const rendered = createElement(Component, props); | ||
| renderedRef.current = rendered; | ||
| prevPropsRef.current = props; | ||
|
|
||
| return rendered; |
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 deps = [..._deps]; | ||
|
|
||
| return useMemo(() => factory, deps); |
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.
만약 하나만 사용하는 변수라면 따로 할당하지 않고, 바로 사용하는 건 어떻게 생각하세요?
return useMemo(() => factory, [..._deps]);| const login = useCallback( | ||
| (email: string) => { | ||
| setUser({ id: 1, name: "홍길동", email }); | ||
| addNotification("성공적으로 로그인되었습니다", "success"); | ||
| }, | ||
| [addNotification], | ||
| ); | ||
|
|
||
| const logout = useCallback(() => { | ||
| setUser(null); | ||
| addNotification("로그아웃되었습니다", "info"); | ||
| }, [addNotification]); |
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.
login 과 logout에 대해서 addNotification을 deps로 두신 이유가 어떤 걸까요? 궁금합니다!
과제 체크포인트
배포 링크
https://hyodduru.github.io/front_5th_chapter1-3/
기본과제
심화 과제
과제 셀프회고
기술적 성장
이번 과제를 진행하면서 React 컴포넌트의 성능 최적화에 대해 한층 더 깊이 이해할 수 있었다.
이전에는 useCallback이나 useMemo 같은 훅들이 내부에서 어떻게 동작하하며 어떤 식으로 성능에 영향을 미치는지 명확하게 이해하지 못했지만, 이번 과제를 통해 그 동작 원리와 실제 적용 효과를 직접 체감할 수 있었다.
특히 인상 깊었던 점은 useCallback이 결국 useMemo의 props로 함수가 들어간 것과 동일한 형태라는 사실을 이제야 명확히 인지하게 된 것이다. 이전에도 자주 사용해왔지만, 이 둘의 구조적 유사성을 별생각 없이 지나쳤다는 점에서 나 스스로도 무관심했음을 느꼈다. 동시에 무의식적으로 사용했던 기술에 대해 더 깊이 이해한 느낌이기도 하다.
이러한 이해가 생기고 나니, React 내부에서 어느 정도 최적화를 자동으로 처리해준다는 점도 납득이 되었고, 덕분에 최적화 적용이 훨씬 수월해졌다.
초반에는 useCallback을 사용하면 무조건 함수가 최적화된다고 막연히 생각했지만, 실제로는 의존성 배열이 변경되지 않을 때에만 동일한 참조를 유지하며 최적화가 일어난다는 점을 직접 확인했다. 이 과정을 통해, props로 함수가 전달될 때 참조 고정이 얼마나 중요한지를 체감할 수 있었고, 자연스럽게 리렌더링 최적화의 핵심 개념들을 실무 관점에서 이해하게 되었다.
무엇보다 중요한 건, 단순히 기술을 사용하는 것보다 “왜 이 상황에서 이 훅을 써야 하는가?”를 스스로 설명할 수 있는 것인 것 같다.
트러블 슈팅
이번 과제에서 가장 오랫동안 붙잡았던 문제다.
✅ 문제 상황
초기 구조는 모든 상태 관리가 App 컴포넌트에 모여 있었다. 로그인/로그아웃 관련 함수(login, logout)도 App 내부에서 선언되어 props로 내려졌고, 알림(Notification)도 별도로 addNotification() 함수를 props 혹은 context로 내려서 사용하고 있었다.
그런데 Header에서 login()을 호출했을 때 Notification이 뜨지 않는 문제가 있었고, 이 때문에 ComplexForm, NotificationSystem이 리렌더링되지 않아서 테스트가 실패하는 상황이 반복되었다.
✅ 원인 분석
이전 구조에서는 Header가 login()만 호출해도 user는 바뀌었지만, 알림(Notification)은 따로 처리하지 않으면 발생하지 않았다.
즉, NotificationContext의 상태가 변하지 않아서, 이 컨텍스트를 구독하고 있는 ComplexForm, NotificationSystem도 리렌더링되지 읺있다.
해결 방법: AuthProvider 내부로 알림 로직 통합
해결의 핵심은 AuthProvider 안에서 NotificationContext를 직접 사용하도록 구조를 바꾼 것이었다.
✅ 기존 구조
App.tsx에서 login, logout, addNotification을 따로 선언하고 각각 내려줌
✅ 변경 후 구조
AuthProvider 내부에서 NotificationContext를 사용하고, login()과 logout()이 알림도 함께 발생시킴
Context 구조를 어떻게 나누고, 그 안에서 책임을 어떻게 위임할지에 따라 리렌더링 성능 최적화 여부가 달라진다는 걸 확인할 수 있었다.
상태 변화의 의미 있는 발생 위치가 어디인지, 그리고 그 변화가 필요한 컴포넌트에만 영향을 주는지까지 고려하는 게 진짜 최적화라는 걸 실감했다.
코드 품질 개선
기능 단위 폴더 분리
상태의 범위 고민
상태관리 리팩토링 (custom hooks)
ItemList의 상태는 원래 App에서 내려받고 있었지만, 이보다는 독립적인 훅인 useItems로 분리하는 방식이 더 자연스럽게 느껴졌다. 필터, 가격 계산 등의 로직도 이 훅으로 이동시켜 UI와 로직을 깔끔하게 나눌 수 있었다.
✅ 이전 - App에서 ItemList를 받아서 props로 전달받는 구조
✅ 이후 - ItemList를 관리하는 useItemList hook을 분리하고 여기서 필요한 상태값들을 전달받도록 변경
학습 효과 분석
이번 프로젝트를 통해 리액트의 렌더링 흐름을 실제로 제어하는 경험을 했다.
useMemo는 값의 재계산을 막기 위한 캐싱 전략
useCallback은 함수 참조의 일관성을 위한 메모이제이션
React.memo는 props가 변경되지 않으면 리렌더링하지 않도록 하는 최종 방어선
이 원리들을 정확히 이해한 상태에서 직접 구조를 리팩토링할 수 있어 좋았고, *"Context는 작게 쪼개고, 관심사는 명확히 분리해야 한다"*는 아키텍처적 인사이트도 얻을 수 있었다.
테스트 기반 개발의 중요성
사실 테스트가 없었다면 ItemList가 은근슬쩍 리렌더링되고 있다는 사실을 몰랐을 수도 있었다. 테스트를 기반으로 성능 이슈를 추적하고, 결과적으로 구조까지 수정하게 된 경험은 TDD의 좋은 예시였다고 생각한다.
리뷰 받고 싶은 부분