Skip to content

[1팀 이은지] Chapter 4-2 코드 관점의 성능 최적화#33

Open
angielxx wants to merge 9 commits into
hanghae-plus:mainfrom
angielxx:main
Open

[1팀 이은지] Chapter 4-2 코드 관점의 성능 최적화#33
angielxx wants to merge 9 commits into
hanghae-plus:mainfrom
angielxx:main

Conversation

@angielxx
Copy link
Copy Markdown

@angielxx angielxx commented Sep 11, 2025

과제 체크포인트

배포 링크

https://angielxx.github.io/front_6th_chapter4-2/

과제 요구사항

  • 배포 후 url 제출

  • API 호출 최적화(Promise.all 이해)

  • SearchDialog 불필요한 연산 최적화

  • SearchDialog 불필요한 리렌더링 최적화

  • 시간표 블록 드래그시 렌더링 최적화

  • 시간표 블록 드롭시 렌더링 최적화

과제 셀프회고

이번 과제는 마지막 주차의 과제라 그런지 과제 진행이 수월하여 빨리 끝낼 수 있었습니다. 그동안 준일 코치님께서 만드셨던 과제를 생각하면 이번 과제는 일부러 마지막이니까 좀 쉬라고(?) 하는 느낌이었습니다. 감사합니다..덕분에 내일 해외여행을 가는데 멀쩡한 컨디션으로 여행을 시작할 수 있을 것 같아요! 수료식에 참여하지 못하는 건 너무 슬프지만요...

이번 과제를 하며 배운 것/꺠달은 것

  1. 최적화를 제대로 하고 있지 않았다.
  2. profiler로 성능 측정하고 렌더링 분석하기
  3. 렌더링 격리

최적화를 제대로 하고 있지 않았다.

심화과제를 진행하면서 드래그앤드롭 시 테이블 자체가 재렌더링되는 문제를 겪었습니다. 드래그 후 드랍하는 순간 테이블이 재렌더링 되는 이유를 profiler의 'How it render?'값을 보고 원인을 파악했습니다. 재렌더링을 발생시키던 원인은 메모이제이션 누락된 prop 한 개 때문이었습니다. 이 현상을 보고 그저 컴포넌트 내부의 함수나 변수를 메모이제이션 하는 걸로는 성능 최적화라는 작업이 완전하진 않겠다고 깨달았습니다.

문제의 메모이제이션 누락된 props onDeleteButtonClick

// src/components/ScheduleTable/ScheduleList.tsx
return (
    <>
 {schedules.map((schedule, index) => (
        <DraggableSchedule
        key={`${tableId}-${index}`} // 안정된 key
        id={`${tableId}:${index}`}
        data={schedule}
        bg={getColor(schedule.lecture.id)}
        onDeleteButtonClick={() => {
            onDeleteButtonClick?.({ day: schedule.day, time: schedule.range[0] });
        }}
        />
    ))}
</>
);

onDeleteButtonClick을 useCallback으로 메모이제이션

// src/components/ScheduleTable/ScheduleList.tsx
const handleDeleteButtonClick = useCallback(
    (day: string, time: number) => () => {
    onDeleteButtonClick?.({ day, time });
    },
    [onDeleteButtonClick]
);

return (
    <>
    {schedules.map((schedule, index) => (
        <DraggableSchedule
        key={`${tableId}-${index}`} // 안정된 key
        id={`${tableId}:${index}`}
        data={schedule}
        bg={getColor(schedule.lecture.id)}
        onDeleteButtonClick={handleDeleteButtonClick(schedule.day, schedule.range[0])}
        />
    ))}
    </>
);

profiler로 성능 측정하고 렌더링 분석하기

이번 과제를 진행하면서 성능 최적화에 대한 인식이 완전히 바뀌었습니다. 단순히 "useMemo 쓰면 빨라진다"는 수준에서 벗어나 체계적으로 문제를 진단하고 해결하는 과정을 경험할 수 있었습니다.

위에서 말한 것처럼 테이블이 재렌더링 되는 이슈를 해결하기 위해 재렌더링의 원인이 무엇인지를 알 수 있는 방법이 없을까 찾아봤고, profiler에서 'Why did this render?'를 확인하면 렌더링 원인을 확인할 수 있다는 걸 알았습니다.

뿐만 아니라 과제의 목표대로 수행하기 위해 과제 내용에 첨부되어 있는 profiler 스크린샷과 제 화면을 비교하기도 했습니다. 처음에는 profile의 FlameGraph를 볼 줄 몰라 디버깅에 어려움이 있었습니다. profiler의 기능을 살펴보고 어떻게 활용하여 성능을 분석하고 활용할 수 있는지 배웠습니다. 사실 profiler를 알고는 있었지만 사용해보지 않은 기능이라 여태껏 공부를 미뤄왔던 부분인데, 이번 기회에 알게 되어 앞으로 실무에서도 잘 활용할 수 있을 것 같아요!

렌더링 격리

이번 과제에서 가장 크게 체감한 건 “리액트에서 성능 최적화 = 불필요한 렌더링의 전파를 끊는 작업”이라는 점이었어요. 이를 위해 컴포넌트를 분리하고, 분리된 경계마다 props를 안정화하여 렌더링을 격리하는 패턴을 집중적으로 연습했습니다.

역할 기준 분리:

  • ScheduleTable를 *정적 그리드(틀)*와 *동적 아이템(드래그되는 블록)*으로 나눴습니다.

  • TableGrid: 셀/라인처럼 상태가 바뀌지 않는 정적 레이아웃만 담당(순수 컴포넌트).

  • ScheduleList: 실제로 변하는 schedules만 구독하여 드래그/삭제 등 상호작용 처리.

렌더링 단위 축소:

  • 아이템 단위 컴포넌트(DraggableSchedule)를 최하위로 내리고 React.memo로 감쌌습니다. 이렇게 하면 schedules 배열의 한 요소가 바뀌어도 그 요소에 해당하는 컴포넌트만 다시 렌더링됩니다.

props 안정화(“격리의 절반은 안정화”):

  • 콜백은 useCallback, 파생값은 useMemo로 고정했습니다.
  • 인라인 객체/배열/함수 생성 금지(= 참조가 매번 바뀌지 않게) → 부모의 재렌더가 자식까지 “도미노”로 번지지 않음.

Profiler의 Flamegraph에서 상위 트리의 흔들림이 크게 줄었고, 드래그/드롭 시 바뀌는 아이템만 다시 그려지는 걸 확인했습니다. 특히 “Why did this render?”에서 props changed 원인이 거의 사라지며, commit 횟수/렌더 시간도 눈에 띄게 감소했습니다.

최종 회고

10주간의 항해 과정을 통해 기술적인 성장 뿐만아니라 스스로 될 때까지 부딪히는 끈기를 기를 수 있었습니다! 10주동안 고생하신 동기 여러분, 그리고 코치님들 너무 감사드려요. 다들 최고 <3 수고하셨습니다!!!

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.

1 participant