리액트에서 렌더링 그리고 리랜더링 이해하기
리액트에서 렌더링은 어떻게 이루어지고, 최적화를 위해 어떤 방법을 사용해야 하는지 알아보겠습니다.
-
서론 • 리액트 렌더링이 중요한 이유 • 최신 리액트의 렌더링 최적화 동향 • 글의 목적 및 다룰 내용
-
리액트 렌더링 기본 개념 • 렌더링이란 무엇인가? • 리액트의 Reconciliation과 Virtual DOM • 리액트 컴포넌트의 생명주기와 렌더링
-
최신 리액트의 주요 렌더링 기술 • Concurrent Mode 개념 소개 • React Server Components • React.memo와 useMemo, useCallback 활용 • Suspense와 Lazy Loading
-
리액트 렌더링 성능 최적화 • 불필요한 렌더링을 방지하는 방법 • Key 사용의 중요성과 올바른 사용법 • 이벤트 핸들러와 상태 관리를 통한 최적화 • Recoil, Zustand와 같은 상태 관리 라이브러리의 비교
-
렌더링 실험 프로젝트 • 프로젝트 소개: 간단한 Todo List 앱 • 실험 목적: 렌더링 최적화 전후 비교 • 실험 환경 세팅: 크롬 개발자 도구, React DevTools, Lighthouse • 실험 지표: 렌더링 시간, 프레임 속도, 메모리 사용량
-
실험 진행 및 데이터 분석 • 최적화 전 Todo List 앱의 성능 측정 • 최적화 기법 적용: React.memo, useCallback 등 • 최적화 후 성능 비교 • 렌더링 성능에 영향을 미친 주요 요인 분석
-
실험 결과 요약 및 인사이트 • 실험 결과 요약 • 최적화 기법의 실제 효과 • 실무에서 렌더링 성능 개선 시 고려할 점
-
결론 • 최신 리액트에서 렌더링 최적화의 중요성 • 앞으로의 리액트 렌더링 기술 전망 • 글을 마치며: 독자에게 전하고 싶은 메시지
목차
서론
리액트 렌더링이 중요한 이유
리액트 핵심은 UI 구성과 상태 관리에 있습니다. 복잡한 어플리케이션 일수록 관리해야하는 상태와 UI 컴포넌트가 많아지는데, 만약 잘못된 방식의 코드로 인행 불필요한 렌더링 이 발생한다면 성능 이슈가 발생할 수 있습니다. 그렇기에 올바른 상태관리 방법과 렌더링 최적화 기법을 알고 사용하는 것이 중요합니다.
최신 리액트의 렌더링 최적화 동향
- Concurrent Mode: 리액트 18에서 새롭게 도입된 Concurrent Mode는 렌더링 우선순위를 조절하여 사용자 경험을 향상시키는 기술입니다.
- React Server Components: 서버에서 렌더링을 처리하는 기술로, 초기 렌더링 속도를 개선합니다.
- React.memo와 useMemo, useCallback 활용: 함수 컴포넌트의 불필요한 렌더링을 방지하는 방법으로, 메모이제이션을 활용합니다.
- Suspense와 Lazy Loading: 데이터 로딩과 컴포넌트 로딩을 분리하여 사용자 경험을 향상시키는 기술입니다.
글의 목적 및 다룰 내용
이 글은 최신 리액트 렌더링 기술과 최적화 방법에 대해 다룹니다. 단순히 개념을 설명하는 데 그치지 않고, 실험 프로젝트를 통해 각 기법이 실제 애플리케이션에서 어떤 영향을 미치는지 데이터로 검증할 것입니다.
- 기초 개념: 리액트 렌더링의 작동 원리
- 최신 기술 소개: Concurrent Mode, React.memo, React Server Components 등
- 성능 최적화 기법: 실무에서 적용 가능한 구체 적인 전략
- 실험 프로젝트: 최적화 전후의 성능 데이터를 비교 분석
리액트 렌더링 기본 개념
렌더링이란 무엇인가?
렌더링은 UI를 화면에 그리는 과정을 의미합니다. 리액트에서는 Virtual DOM을 활용하여 렌더링을 처리하며, 이를 통해 렌더링 성능을 최적화합니다.
- 렌더(Render) 단계: 어떤 UI가 필요한지 계산하고, 이 과정을 여러 번 반복하거나 일시 정지할 수도 있음
- 커밋(Commit) 단계: 계산된 변경 사항을 실제 DOM에 적용하고, 화면에 반영
특히 Concurrent Rendering(동시적 렌더링) 시나리오에서 “렌더”가 여러 번 시도될 수 있지만, “커밋”은 한 번에 반영된다는 점이 핵심입니다.
- 렌더 단계에서 여러 버전을 시도해보고 최종 UI를 결정
- 커밋 단계에서 실제 DOM에 반영하여 사용자에게 보이는 부분을 업데이트
자세한 내용은 React 공식 문서를 참고하세요.
최신 리액트의 주요 렌더링 기술
Concurrent Mode 개념 소개
Concurrent Mode는 리액트 애플리케이션이 렌더링 작업을 여러 단위로 쪼개어 수행하고, 사용자의 상호작용(입력, 클릭 등)을 우선 처리할 수 있도록 지원하는 새로운 렌더링 모드입니다.
- 우선순위 기반 렌더링: 긴 렌더링 작업 중에도 사용자의 입력이나 중요한 작업이 들어오면, 먼저 처리한 뒤 다시 렌더링을 이어갑니다.
- 부드러운 사용자 경험: 대규모 컴포넌트 트리를 렌더링할 때 발생할 수 있는 UI 정지 시간을 최소화합니다.
- Backpressure 제어: 작업이 많은 경우에도 화면이 ‘끊기지 않도록’ 효율적으로 분할 처리하므로, 애플리케이션 응답성을 높여줍니다.
React Server Components
서버 컴포넌트는 번들링 전에 클라이언트 앱이나 SSR(Server Side Rendering) 서버와는 분리된 환경에서 미리 렌더링되는 새로운 유형의 컴포넌트입니다.
자세한 내용은 React 공식 문서를 참고하세요.
React.memo와 useMemo, useCallback 활용
React.memo, useMemo, useCallback 등의 훅을 활용하여 함수 컴포넌트의 불필요한 렌더링을 방지할 수 있습니다.
- React.memo: 컴포넌트의 props가 변경되지 않으면 렌더링을 스킵합니다.
- useMemo: 계산 비용이 높은 값을 캐싱하여 불필요한 재계산을 방지합니다.
- useCallback: 이벤트 핸들러나 콜백 함수를 메모이제이션하여 불필요한 렌더링을 방지합니다.
성능 최적화를 위해서만useMemo를 사용해야 합니다. 이 기능이 없어서 코드가 작동하지 않는다면 근본적인 문제를 먼저 찾아서 수정하세요. 그 후 useMemo를 사용하여 성능을 개선해야 합니다.
앱의 속도를 실제로 저하시키는 요인을 현실적으로 파악하려면 프로덕션 모드에서 React를 실행하고, React 개발자 도구를 비활성화하고, 앱 사용자가 사용하는 것과 유사한 기기를 사용해야 한다는 점을 명심하세요.
자세한 내용은 React 공식 문서 useMemo를 참고하세요. useCallback도 참고하세요.
Suspense와 Lazy Loading
Suspense는 데이터 로딩과 컴포넌트 로딩을 분리하여 사용자 경험을 향상시키는 기술입니다. Lazy Loading은 필요한 시점에 컴포넌트를 동적으로 로딩하여 초기 렌더링 속도를 개선합니다.
자세한 내용은 React 공식 문서 Suspense를 참고하세요.
리액트 렌더링 성능 최적화
불필요한 렌더링을 방지하는 방법
리액트에서 불필요한 렌더링을 방지하는 방법은 다음과 같습니다.
- 메모이제이션 활용
- React.memo: 함수형 컴포넌트를 감싸서 props가 변화하지 않으면 재렌더링을 건너뜁니다.
- useMemo, useCallback: 계산 비용이 큰 연산이나 이벤트 핸들러 함수를 메모이제이션해, 의존성이 바뀌지 않으면 이전 값을 재사용합니다.
- 컴포넌트 분리
- 큰 컴포넌트를 기능 단위로 나누면, 리렌더링 범위를 줄일 수 있습니다.
- 자식 컴포넌트가 불필요하게 부모 상태에 의존하지 않도록 구조를 개선합니다.
- 최소한의 state 관리
- 필요 없는 곳 까지 state를 공유하면 전체 컴포넌트 트리가 영향을 받아 과도한 렌더링을 유발합니다.
- 상태가 변경될 때마다 해당 상태를 참조하는 컴포넌트만 렌더링되도록 상태 범위를 최소화합니다.
Key 사용의 중요성과 올바른 사용법
리액트에서 동적으로 생성되는 컴포넌트 리스트를 렌더링할 때는 각 항목에 고유한 key를 지정해야 합니다. key가 없거나 중복된 key를 사용하면 리액트가 컴포넌트를 올바르게 비교하지 못해 성능 문제가 발생할 수 있습니다.
- key는 컴포넌트의 고유성을 식별하는 역할을 합니다.
- key는 컴포넌트의 순서가 변경되어도 올바르게 비교할 수 있도록 도와줍니다. (index를 key로 사용하는 것은 권장되지 않습니다.)
전역 상태 관리 라이브러리 vs context API
Context API의 특징
- 리액트 기본 내장 기능 Context API는 리액트 자체적으로 제공되므로 추가 라이브러리 설치가 필요 없습니다. createContext, useContext로 쉽게 전역 상태를 공유할 수 있습니다.
- 전체 렌더링 범위 Context Provider로 감싸진 영역 내의 모든 컴포넌트는 Context 값 을 구독하게 됩니다. 값이 변경되면 해당 Context를 사용하는 모든 자식 컴포넌트가 렌더링될 수 있습니다. 프로젝트 규모가 커질수록, 불필요한 렌더링이 늘어날 위험이 있습니다.
- 간단한 구조 구조가 단순하고 직관적이어서, 작은 프로젝트나 상태 공유 범위가 크지 않은 경우에는 간편하게 적용할 수 있습니다.
전역 상태 관리 라이브러리의 특징 (ex zuistand)
Zustand
- 필요한 부분만 구독 zustand는 React Context를 내부적으로 사용하지만, 상태를 사용하는 컴포넌트만 구독하도록 설계되어 있습니다.
- 간단한 API store 정의 후, 필요한 곳에서 useStore 같은 훅을 통해 편리하게 사용하는 방식이라 코드 분리가 깔끔합니다.
- 성능 최적화 전역 상태가 업데이트될 때, 관련된 컴포넌트만 업데이트되므로 불필요한 렌더링을 최소화합니다.
선택 기준
- 작은 프로젝트나 간단한 상태 공유라면 Context API만으로 충분할 수 있습니다.
- 프로젝트 규모가 커지거나 상태 관리가 복잡해지면, 전역 상태 관리 라이브러리를 사용하여 불필요한 렌더링을 최소화할 수 있습니다.
- 비동기 로직이나 복잡한 사이드 이펙트 처리가 많다면, 미들웨어 체계가 갖춰진 Redux가 유리할 수 있습니다.
- 전역 상태관리는 라이브러리를 사용하고, 부분적으로 Context API를 활용하는 방식도 가능합니다.
렌더링 실험 프로젝트
프로젝트 소개: 간단한 Todo List 앱
아래와 같은 기능을 갖춘 Todo List를 예시로 들어보겠습니다.
- 항목 추가: 입력란에 텍스트를 입력하고 Enter 키를 누르거나 “추가” 버튼을 클릭해 항목을 생성.
- 항목 삭제: 각 항목 옆의 “삭제” 버튼을 누르면 해당 항목이 삭제됨.
- 항목 완료 처리: 체크박스를 클릭해 완료 여부 표시.
- 검색 및 필터: 원하는 키워드로 항목을 검색하거나, 완료 항목만 표시.
이 앱을 두 가지 버전으로 구현해 비교할 예정입니다.
- 최적화 전: 메모이제이션(React.memo, useCallback, useMemo)이나 상태 관리 최적화 등을 적용하지 않은 기본 구현.
- 최적화 후: 불필요한 렌더링을 줄이기 위한 기술들을 적용한 버전.
실험 환경 세팅: 크롬 개발자 도구, React DevTools, Lighthouse
-
크롬 개발자 도구
- Performance 탭: 렌더링 과정, CPU 사용량, FPS 확인.
- Memory 탭: 힙 스냅샷(Heap Snapshot)을 통해 메모리 사용량 파악 가능.
-
React DevTools
- Profiler 탭: 각 컴포넌트가 몇 번 렌더링되었는지, 렌더링에 소요된 시간은 얼마인지 확인할 수 있음.
-
Lighthouse
- Performance 측정: TTI(Time to Interactive), FCP(First Contentful Paint) 같은 지표를 살펴볼 수 있음.
- Best Practices, SEO: 성능 외에도 웹 품질 종합 평가 가능.
실험 지표
- 렌더링 시간(Rendering Time)
- 크롬 개발자 도구 Performance나 React DevTools Profiler를 통해 측정.
- 컴포넌트가 업데이트될 때마다 걸리는 시간을 비교해 불필요한 렌더링이 줄었는지 확인.
- 프레임 속도(FPS)
- 많은 항목을 생성하거나, 리스트를 빠르게 스크롤할 때 FPS가 떨어지는지 관찰.
- 애니메이션 또는 사용자 인터랙션 중 렉이 발생하지 않는지 체크.
- 메모리 사용량(Memory Usage)
- 대량의 Todo를 추가할 때 메모리 사용량이 어떠한지 확인.
- 부주의한 상태 관리로 메모리가 누수되거나, 지나치게 많은 컴포넌트가 생성되지 않는지 파악.
실험 진행 및 데이터 분석
- vite로 프로젝트 생성 (React, typescript)
$ yarn create vite react-todo_render --template react-ts