Amelia-Shin 님의 상세페이지[1팀 신희원] Chapter 4-2 코드 관점의 성능 최적화

과제 체크포인트

배포링크: https://amelia-shin.github.io/front_6th_chapter4-2/

과제 요구사항

  • 배포 후 url 제출

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

  • SearchDialog 불필요한 연산 최적화

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

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

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

과제 셀프회고

기술적 성장

1. Promise.all의 올바른 사용법

  • 문제점 발견: 기존 코드에서 Promise.all 안에서 await를 사용하여 병렬성 파괴
// 문제가 있던 코드
const fetchAllLectures = async () =>
  await Promise.all([
    await fetchMajors(), // 순차 실행됨
    await fetchLiberalArts(),
    // ...
  ]);
  • 해결 방법: await 제거하여 진정한 병렬 실행 구현
// 개선된 코드
const fetchAllLectures = async () => {
  const [majorsResult, liberalArtsResult] = await Promise.all([
    fetchMajors(), // 병렬 실행
    fetchLiberalArts(),
  ]);
  return [...majorsResult.data, ...liberalArtsResult.data];
};

2. React 메모이제이션 전략

  • React.memo: 컴포넌트 레벨에서 불필요한 리렌더링 방지
  • useMemo: 계산 비용이 큰 연산 결과 캐싱
  • useCallback: 함수 참조 안정화로 자식 컴포넌트 리렌더링 최적화
  • useAutoCallback: 커스텀 훅으로 함수 참조 문제 해결

3. 캐시 관리 시스템 구축

  • TTL(Time To Live) 기반 캐시: 시간 기반 캐시 만료 정책
  • 중복 요청 방지: 동일한 요청이 진행 중일 때 대기 메커니즘
  • 캐시 통계: 캐시 히트율 및 성능 모니터링

4. 컴포넌트 분리 전략

  • 단일 책임 원칙: 각 컴포넌트가 하나의 명확한 역할만 담당
  • 커스텀 훅 패턴: 비즈니스 로직과 UI 로직 분리
  • 관심사 분리: 검색, 필터링, 무한스크롤 등 기능별 모듈화

코드 품질

1. useReducer vs useState

  • 복잡한 상태 관리: 여러 필드가 연관된 상태에서는 useReducer가 더 적합
  • 성능 최적화: useReducer의 dispatch는 항상 동일한 참조를 유지
  • 타입 안정성: 액션 기반 상태 변경으로 타입 안전성 향상

2. 디바운싱의 중요성

  • 사용자 경험: 입력 중 불필요한 API 호출 방지
  • 성능 최적화: 검색어 입력 시 실시간 필터링 대신 지연된 처리
  • 리소스 절약: 서버 부하 감소 및 네트워크 트래픽 최적화

학습 효과 분석

기존 코드 (searchDialog.tsx)

  1. Promise.all 안에서 await 사용 (병렬성 파괴)

문제점 : 이렇게 하면 각 API 호출이 순차적으로 실행.

const fetchAllLectures = async () =>
  await Promise.all([
    (console.log("API Call 1", performance.now()), await fetchMajors()),
    (console.log("API Call 2", performance.now()), await fetchLiberalArts()),
    (console.log("API Call 3", performance.now()), await fetchMajors()),
    (console.log("API Call 4", performance.now()), await fetchLiberalArts()),
    (console.log("API Call 5", performance.now()), await fetchMajors()),
    (console.log("API Call 6", performance.now()), await fetchLiberalArts()),
  ]);

image [await 사용] image [await 제거]

가장 큰 배움이 있었던 부분

1. 성능 최적화의 체계적 접근

  • 측정 우선: 최적화 전후 성능 측정의 중요성
  • 점진적 개선: 한 번에 모든 것을 최적화하지 않고 단계적 접근
  • 사용자 경험 중심: 기술적 최적화보다 사용자 경험 개선에 집중

2. React 생태계의 깊은 이해

  • 렌더링 메커니즘: React의 렌더링 사이클과 최적화 포인트
  • 메모이제이션 전략: 언제, 어떻게 메모이제이션을 적용할지
  • 컴포넌트 설계: 재사용 가능하고 테스트 가능한 컴포넌트 설계

3. 실무에서의 성능 고려사항

  • 네트워크 최적화: API 호출 최소화 및 캐싱 전략
  • 번들 크기: 불필요한 라이브러리 제거 및 코드 스플리팅
  • 사용자 피드백: 로딩 상태 및 에러 처리의 중요성

기타사항 렌더링을 유발하는 요소 : 객체/배열/함수 메모이제이션 해서 보내거나, 아니면 최대한 직접 값 자체를 보내거나해야한다.

리팩토링이 필요한 부분

1. 타입 정의 중복

  • 문제: Lecture 타입이 여러 파일에 중복 정의
  • 개선 방향: 공통 타입 파일로 통합 필요

2. 에러 처리 부족

  • 문제: API 호출 실패 시 사용자 피드백 부족
  • 개선 방향: 에러 바운더리 및 로딩 상태 관리 강화

3. 접근성(Accessibility) 고려 부족

  • 문제: 키보드 네비게이션 및 스크린 리더 지원 미흡
  • 개선 방향: ARIA 속성 및 키보드 이벤트 처리 추가

실무 적용 가능성

1. 즉시 적용 가능한 기술

  • 메모이제이션: 성능이 중요한 컴포넌트에 즉시 적용
  • 컴포넌트 분리: 유지보수성 향상을 위한 아키텍처 개선
  • 캐싱 전략: API 호출 최적화로 서버 비용 절약

2. 장기적 개선 방향

  • 마이크로 프론트엔드: 대규모 애플리케이션의 모듈화
  • 성능 모니터링: 실시간 성능 지표 수집 및 분석
  • 접근성 개선: 웹 접근성 가이드라인 준수

3. 팀 차원의 적용

  • 코드 리뷰: 성능 최적화 관점의 코드 리뷰 프로세스
  • 성능 가이드라인: 팀 내 성능 최적화 가이드라인 수립
  • 지식 공유: 성능 최적화 경험 및 노하우 공유

과제 피드백

과제에서 좋았던 부분

1. 실무 중심의 문제 설정

  • 장점: 실제 개발에서 마주치는 성능 문제를 다룸
  • 효과: 실무 적용 가능한 경험 습득

2. 점진적 개선 과정

  • 장점: 한 번에 모든 것을 바꾸지 않고 단계적 개선
  • 효과: 안정적인 리팩토링과 학습 효과 극대화

3. 성능 측정 도구 활용

  • 장점: React DevTools, Performance API 등 실제 도구 사용
  • 효과: 정량적 성능 개선 확인 가능

리뷰 받고 싶은 내용

성능 최적화

  • 메모이제이션: React.memo, useMemo, useCallback을 어디에 적용해야 하는지 판단 기준
  • 렌더링 최적화: 모든 컴포넌트에 React.memo를 적용하는 것이 좋은가?
  • 계산 비용: useMemo를 언제 사용해야 하는지, 계산 비용이 얼마나 클 때 사용해야 하는가?

Context API 활용

  • 전역 상태 관리: ScheduleContext를 사용한 것이 과도한 전역 상태 관리인지
  • props drilling vs Context: 이런 경우에 Context API 대신 props drilling을 사용하는 것이 나은가?
  • Context 도입 시점: 언제 Context API를 도입해야 하는가?

상태 관리

  • useReducer vs useState: 복잡한 상태에서 useReducer를 사용한 것이 적절한가?
  • 상태 설계: 상태가 몇 개의 필드 이상일 때 useReducer를 고려해야 하는가?
  • 상태 정규화: 복잡한 상태 구조를 어떻게 설계해야 하는가?

10주간 함께 해주셔서 감사합니다 :-) 고생많으셨고 건강하시고 행복하세요 ~

과제 피드백

안녕하세요 희원님! 마지막 과제 너무 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!!


메모이제이션: React.memo, useMemo, useCallback을 어디에 적용해야 하는지 판단 기준

항상 적용할 필요는 없고, 대체로 memo를 한 번 적용하기 시작하면, memo에 전달하는 props를 메모이제이션 하기위해 연달아 useMemo, useCallback 을 사용해서 props로 내려주는 연계 과정이 필요합니다.

서로 연결이 되어있는거죠.. ㅎㅎ 그리고 memo는 렌더링이 많이 발생하는 컴포넌트에 적용하면 되겠죠..!? 특별한 정답이 있진 않다고 생각해요.

렌더링 최적화: 모든 컴포넌트에 React.memo를 적용하는 것이 좋은가?

커뮤니케이션을 할 때 유용하답니다 ㅋㅋ "우리는 그냥 다 메모이제이션해~" 라고 의사결정을 하고 진행하면 고민을 하는 비용을 줄일 수 있어요.

계산 비용: useMemo를 언제 사용해야 하는지, 계산 비용이 얼마나 클 때 사용해야 하는가?

대체로 배열과 관련된 연산을 할 때 유용해요! 한 번의 연산이 16.66ms 이상 걸린다면... (1frame) 적용할 필요가 있다고 생각합니다.

전역 상태 관리: ScheduleContext를 사용한 것이 과도한 전역 상태 관리인지

지금은 과도하지 않다고 생각해요 ㅎㅎ 다만 이 context를 의존하는 컴포넌트가 많을 때에는 과도할 수 있죠.. 아이러니 하지만 전역상태를 의존하는 컴포넌트가 많다는건 전역상태가 변경되었을 때 렌더링이 전파된다는 이야기이니까요.

props drilling vs Context: 이런 경우에 Context API 대신 props drilling을 사용하는 것이 나은가?

무조건 좋은건 없어요. 상황에 따라 다르답니다! context api의 경우 특히 디자인 시스템을 만들 때 유용해요 ㅎㅎ

Context 도입 시점: 언제 Context API를 도입해야 하는가?

전역상태로 사용할 때 보단... 특정 컴포넌트에 필요한 데이터를 정의할때 유용해요. 가령 폼데이터라거나?

useReducer vs useState: 복잡한 상태에서 useReducer를 사용한 것이 적절한가?

적절하다고 생각합니다. 테스트 하기가 좋아요!

상태 설계: 상태가 몇 개의 필드 이상일 때 useReducer를 고려해야 하는가?

한 번에 엮여서 쓰이는게 3~4개 이상일 때 적절하지 않을까요!? 이건 reducer를 많이 써보면서 익혀야 한다고 생각해요.

상태 정규화: 복잡한 상태 구조를 어떻게 설계해야 하는가?

흠... 어떤 모습의 상태냐에 따라 다르겠네요.. ㅎㅎ

질문이 대체로 포괄적이여서 양질의 답변을 드리기가 조금 어렵네요 ㅎㅎ ㅠㅠ 대체로 정답이 있는 질문이 아니라서요. 희원님께서 경험을 해보고 판단을 하는 과정이 필요하다고 생각합니다!