React의 렌더링 프로세스 알아보기

작성일: 2025년 12월 12일 오전 08:25(마지막 수정: 2026년 3월 23일 오후 05:41)
조회수: 72

개요

React는 UI 라이브러리(혹은 프레임워크)로, JSX를 return하는 다양한 컴포넌트(함수)를 이용하여 DOM을 효율적으로 제어할 수 있다는 장점이 있습니다.

하지만 React 내부적으로 어떻게 DOM을 렌더링하고 업데이트하는지 설명할 수 있나요?

어쩌면 가볍게 넘어갔을 내용일 수 있습니다. 굳이 이를 자세히 모르더라도 React를 사용하는데에 큰 문제는 없으니까요.

하지만 '개발자'로서, 내가 사용하는 라이브러리가 어떤 식으로 동작하는지 '어느 정도'는 알아야겠다면 이 글을 참고해보시길 바랍니다.

가장 먼저 짚고 가야 할 부분이 있습니다.

공식 문서에서 따르면 브라우저 렌더링과 React에서 말하는 렌더링은 다릅니다. React에서 렌더링은 컴포넌트를 호출하는 것입니다.


Trigger -> Render -> Commit

스크린샷 2025-12-12 오전 11.55.44.png

React에서 컴포넌트를 DOM으로 보여주는 방식은 크게 3단계로 진행됩니다.

  • Trigger Phase: 초기 렌더링 혹은 컴포넌트이 state가 업데이트되었을 때 렌더링을 트리거합니다.
  • Render Phase: 컴포넌트를 호출(call)하여 화면에 표시할 내용을 '파악'합니다.
  • Commit Phase: DOM을 '최소한'만 업데이트(브라우저 렌더링)합니다.

한 단계씩 알아봅시다.


1) Trigger Phase: 렌더링을 trigger

위에서 언급했듯이, 렌더링을 trigger하는 경우는 크게 두 가지가 있습니다.

  • 앱을 시작할 때 렌더링
  • state를 업데이트(set함수)했을 때 리렌더링

1-1) 앱을 시작할 때: 초기 렌더링 trigger

먼저 앱을 시작할 때는, App을 렌더링해서 화면에 보여줘야겠죠? 때문에 렌더링을 trigger합니다.

이는 보통 React 프로젝트를 시작하면 기본 설정되는 다음과 같은 코드를 통해 확인할 수 있습니다.

jsx
// 공식 문서 예시
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<App />);

React는 createRoot를 호출하여 브라우저 DOM 노드 안에 React 컴포넌트를 표시하는 root를 생성합니다.

React는 root를 생성하고 그 안에 있는 DOM을 관리합니다.

그 root 컴포넌트로 render 메서드를 호출하면서 렌더링을 trigger하게 됩니다.

이를 직접 확인하는 방법은 root.render를 삭제해보세요. App 컴포넌트가 사라지는 것을 확인할 수 있습니다.

1-2) state가 업데이트되었을 때 리렌더링 trigger

초기 렌더링 이후라도, 여러분들도 알다시피 setState와 같은 set함수를 통해 상태(state)를 업데이트하여 추가적인 렌더링을 trigger할 수 있습니다.

만약 상태가 업데이트되면 자동으로 렌더링 대기열(큐)에 해당 컴포넌트가 추가됩니다.

스크린샷 2025-12-12 오후 3.47.39.png


2) Render Phase: React 컴포넌트 렌더링

렌더링이 trigger 되었으면 렌더링해야겠죠?(root 컴포넌트 혹은 상태가 업데이트된 컴포넌트)

Render Phase는 간단히 정리하면 다음과 같습니다.

  • 컴파일되는 과정에서 JSX가 React.createElement() 형태로 변환
  • 이를 호출하며 return하는 JS 객체(생성해야 하는 UI(DOM) 구조)를 재귀적(부모 -> 자식)으로 생성하여, 최종적으로 트리 형태의 Virtual DOM을 생성
  • 생성된 new Virtual DOM과 이전 Render Phase 때 생성되었던 prev Virtual DOM과 비교(diffing)하여 변경사항 목록을 수집하는 재조정(Reconciliation)

위 과정을 하나씩 살펴봅시다.

2-1) 컴파일되는 과정에서 JSX가 React.createElement() 형태로 변환

다들 JSX 문법을 아무렇지 않게 사용했을 수도 있습니다만, 이는 순수 JS의 문법이 아닙니다.

평소 어떻게 브라우저가 이 코드를 이해할지 의문이 들었었다면 아! 할만한 과정입니다.

맞습니다! React가 내부적으로 빌드할 때 브라우저가 이해할 수 있는 Javascript 코드로 바꿔주는 부분이 이 부분입니다.

2-2) 이를 호출하며 return하는 JS 객체(Fiber, 혹은 생성해야 하는 UI(DOM) 구조)를 재귀적(부모 -> 자식)으로 생성하여, 최종적으로 트리 형태의 Virtual DOM을 생성

이 부분이 '브라우저 렌더링'과 'React에서 정의하는 렌더링'이 다른 이유입니다.

React에서의 렌더링은 DOM을 생성하는 것을 의미하지 않습니다. 그저 컴포넌트를 재귀적으로 호출하면서 JS 객체 트리 즉, Virtual DOM을 생성할 뿐이죠.

Virtual DOM은 개념적으로 'DOM'이라 표현한거지, 실제로 존재하는 DOM 노드가 아닙니다.

실제로 React 개발자도 virtual DOM 용어를 폐기할 것을 권고하고 있습니다. value UI로 표현하기를 권고합니다.

2-3) 생성된 new Virtual DOM과 이전 Render Phase 때 생성되었던 prev Virtual DOM과 비교하여 변경사항 목록을 수집하는 재조정(Reconciliation)

이전에 렌더링한 적이 있다면, 그 때 생성된 'prev Virtual DOM'은 버려지지 않고 메모리에 저장되며 React 라이브러리에 의해 관리됩니다.

그리고 이번 Render Phase 때 새롭게 생성된 'new Virtual DOM'을 이와 Diffing 알고리즘을 통해 비교하여, 어떤 DOM을 실제로 업데이트해야 하는지 체크합니다.

그런데, 이 둘은 복잡한 객체일텐데 비교하는 속도가 매우 느리지 않을까 하는 걱정이 듭니다. 실제로 diffing 알고리즘은 시간 복잡도가 최소 O(n^3)으로 알려져 있습니다.

이를 해결하기 위해 React는 diffing 알고리즘을 사용하되, '휴리스틱 알고리즘'을 적용하여 두 Virtual DOM을 조금 느슨하게 비교하도록 합니다.

참고) 휴리스틱 알고리즘: 최적의 답을 보장하진 않아도 어림짐작을 활용하여 충분히 좋은 근사 해를 찾는 문제 해결 방법

다음과 같은 원칙을 전제로 합니다.

첫째, 서로 다른 타입의 두 요소는 서로 다른 트리를 만들어낸다. 둘째, 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

이로 인해 다음과 같은 '가정'을 하여 O(n)의 휴리스틱을 달성합니다.

첫째, 이전과 다른 타입의 React 엘리먼트로 교체되었다면 하위 트리는 더 이상 비교하지 않고 전체를 교체한다. 둘째, key가 동일한 React 엘리먼트는 이전과 동일한 엘리먼트로 취급한다.

이러한 과정 즉, 재조정(Reconciliation)을 통해 변경해야 할 DOM을 결정할 수 있게 됩니다.


3) Commit Phase: React가 DOM에 변경 사항을 커밋(painting)

마지막으로 Commit Phase입니다. 다들 git 사용하실 때 commit 한 번쯤 사용해보셨죠?

git commit 명령어를 사용하면 그 당시의 코드 스냅샷이 기록되어 확정되듯이,

두 Virtual DOM(두 렌더링) 간의 비교를 통해 얻은 변경 사항을 '실제 DOM'에 적용합니다.

여기서 적용은 노드를 생성, 수정 및 삭제하는 것을 의미합니다.

이게 끝일까요? 아직 사용자는 변경점을 브라우저에서 확인하지 못했습니다.

3-1) 마지막.. 브라우저가 요소를 화면에 그리기

왜 그런지는(아시는 분도 있겠지만), 브라우저가 요소를 렌더링하는 과정인 Critical Rendering Path를 살펴보면 알 수 있습니다.

스크린샷 2025-12-12 오후 5.07.12.png

그렇습니다. 아직 Javascript에 의해 DOM까지만 변경되었죠.

그러므로 브라우저는 최종 DOM tree를 기반으로 화면을 다시 그리게 됩니다.

3-2) Critical Rendering Path - React 렌더링 프로세스의 효율성 엿보기

브라우저 렌더링 과정을 확인한 김에 성능 측면에서 React의 렌더링 프로세스가 얼마나 효율적인지 알아볼까요?

여기서 중요하게 봐야할 단계가 있는데, 바로 LayoutPainting입니다.

이 두 과정은 '매우 비싼(연산이 복잡)` 과정입니다.

때문에 **Layout 단계를 다시 하는 것(Reflow)**과 **Painting을 다시 하는 것(Repaint)**을 최대한 줄여주는 것이 좋겠죠. 이를 위해서는 '업데이트할 DOM을 최소화'하는 것이 중요합니다.

그렇기 때문에 React는, 2단계에서 살펴봤던 **재조정(Reconciliation)**을 통해, '변경된 DOM'만 골라내어 최대한 효율적으로 브라우저가 렌더링하도록 하는 것입니다.

이 글에서는 다루지 않지만 이 외에도, React는 Batching 전략(여러 state의 변화를 하나의 리렌더링으로 묶어 수행하는 방식) 등등으로 브라우저 렌더링 과정을 최적화합니다.


참고자료

두서 없지만 나름 장황한 글을 읽어주셔서 감사합니다. 더 자세한 내용은 아래를 참고해주세요.

0개의 댓글
💬

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!