Skip to content

Conversation

@kimfriendship
Copy link

@kimfriendship kimfriendship commented Dec 22, 2025

과제 체크포인트

배포 링크

과제 요구사항

  • 배포 후 url 제출
  • API 호출 최적화(Promise.all 이해)
  • SearchDialog 불필요한 연산 최적화
  • SearchDialog 불필요한 리렌더링 최적화
  • 시간표 블록 드래그시 렌더링 최적화
  • 시간표 블록 드롭시 렌더링 최적화

과제 셀프회고

1. SearchDialog API 호출 최적화

(1) 직렬 호출 → 병렬 호출
기존에는 각 API 호출 함수 내부에서 await을 사용해 직렬로 요청이 수행되고 있었습니다.
이를 Promise.all을 사용해 병렬 호출로 변경하여 초기 로딩 시간을 단축했습니다.

(2) 클로저를 활용한 캐싱
최초 구현에서는 async/await으로 응답을 받은 뒤 캐싱했지만, 이 방식은 병렬 호출 상황에서는 중복 요청을 막지 못했습니다.
클로저 내부에 Promise 자체를 캐싱하는 방식으로 변경하여, 동일 요청에 대해 하나의 Promise를 공유하도록 개선했습니다.
결과적으로 동일 API의 중복 호출을 제거할 수 있었고, 체감 성능이 크게 개선되었습니다.

AS IS TO BE
image image
142 밀리초 69 밀리초

2. 불필요한 연산 최적화

검색 조건이 변경되지 않는 한 필터링 연산이 반복되지 않도록 개선하였습니다.

  • 검색 로직(getFilteredLectures)에 useCallback 적용
  • 검색 결과(filteredLectures)에 useMemo 적용
  const getFilteredLectures = useCallback(() => {
    const { query = "", credits, grades, days, times, majors } = searchOptions;
    return ...
  }, [lectures, searchOptions]);

  const filteredLectures = useMemo(
    () => getFilteredLectures(),
    [getFilteredLectures]
  );
  const allMajors = useMemo(
    () => [...new Set(lectures.map((lecture) => lecture.major))],
    [lectures]
  );

3. 불필요한 렌더링 방지 (검색 결과 테이블)

스크롤 시 tbody 하위 컴포넌트 전체가 다시 렌더링되는 문제가 있었고, 페이지가 뒤로 갈수록 누적된 아이템들로 인해 점점 더 느려지는 현상이 있었습니다.

  • LectureListItem 컴포넌트를 분리하고 React.memo 적용하고, props로 전달되는 함수들을 useCallback으로 고정하자 새로 추가된 아이템만 렌더링되는 것을 확인할 수 있었습니다.
  • 또한 스크롤 이벤트로 인해 모달 전체가 리렌더링되는 문제를 발견하여, 스크롤로 업데이트되는 영역(LectureList)과 나머지 UI 영역을 분리하여 컴포넌트화했습니다.
AS IS TO BE
image image
렌더링 498 밀리초 렌더링 204 밀리초

4. 드래그 시 렌더링 최적화

드래그 중 모든 시간표 테이블이 리렌더링되고 있어서 현재 드래그 대상이 되는 테이블만 영향을 받도록 개선 시도하였습니다.

  • ScheduleDndProvider의 위치를 점점 좁혀가며 렌더링 범위를 축소
  • 너무 하위로 내렸을 경우 useDndContext가 정상 동작하지 않는 문제(드래그 아웃라인 미노출)를 발견
  • Provider 위치를 다시 조정하여 기능과 성능의 균형을 맞춤

그런데 아직 드래그 중 테이블 내부의 다른 아이템들과 popover까지 계속 리렌더링되는 문제가 존재했습니다.

  • table grid를 컴포넌트로 분리하고 React.memo 적용
  • popover는 아이템 단위로 유지하되, 내부 텍스트/액션 영역을 분리
  • onDelete 함수 참조를 최상위에서 useCallback으로 고정
  • 자식 컴포넌트에서 파라미터를 받아 사용하는 방식으로 변경
  • Popover에 isLazy 옵션을 적용
AS IS TO BE
image image

👉 이 과정에서 함수 props의 참조 안정성이 렌더링에 미치는 영향을 명확히 확인할 수 있었습니다.

5. 드롭 시 렌더링 최적화

Context가 변경되면 결국 모든 구독 컴포넌트가 영향을 받는 구조적 한계 개선

  • ScheduleTables에 테이블 삭제, 복제 기능과 스케줄 데이터들을 맵으로 렌더링 시켜주기 위해 schedulesMap을 알아야만 하는 상황이었고, 그래서 전역상태로 schedulesMap을 갖되 ScheduleTables에서는 tableId만 구독하도록 했습니다.
  • 나머지 삭제, 복제 로직도 schedulesMap을 컴포넌트단에서 직접 참조하지 않도록 하였습니다.
  • 드랍하는 시점에서 수정되지 않은 테이블도 새로운 객체로 반환하고 있어 모든 테이블이 리렌더링되고 있었는데, 수정되지 않은 테이블은 기존 참조를 유지할 수 있게 기존 객체를 반환하게 수정하였습니다.
AS IS TO BE
image image
렌더링 292 밀리초 렌더링 4 밀리초

👉 이 과정에서, “하나라도 리렌더링을 유발하는 요소가 있으면 성능 개선이 어렵다”는 걸 체감하고, 준일 코치님이 모든 것을 메모이제이션하는 것도 좋은 방법이라고 하셨던 이유를 몸소 이해하게 됐습니다.
👉 또 useCallback으로 감싼 함수더라도, 자식 컴포넌트로 넘겨줄 때 파라미터를 포함한 함수로 인라인 작성하면 매번 새롭게 생성되어 리렌더링이 발생하는 걸 확인했습니다.

리뷰 받고 싶은 내용

React.memo, useCallback, useMemo를 적용해도 여전히 리렌더링이 발생하는 경우가 많았는데요.
코치님은 이런 상황에서 “메모이제이션을 더 할지, 구조를 바꿀지”를 어떻게 판단하시나요?

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