Render and Commit

김동현·2026년 3월 15일

title: 렌더링과 커밋 (Render and Commit)

컴포넌트가 여러분의 화면에 표시되기 전에, React는 반드시 컴포넌트를 렌더링(Render) 해야 합니다. 이 과정이 어떤 단계로 이루어지는지 이해하게 되면, 여러분이 작성한 코드가 어떻게 실행되는지 사고하는 흐름을 잡을 수 있고, 예상치 못한 동작이 발생했을 때 그 원인을 쉽게 파악할 수 있게 됩니다.

💡 이 장에서 배울 내용

  • React에서 렌더링이 무엇을 의미하는지
  • React가 언제, 그리고 왜 컴포넌트를 렌더링하는지
  • 컴포넌트가 화면에 표시되기까지의 단계들
  • 렌더링이 일어난다고 해서 항상 DOM 업데이트가 발생하지는 않는 이유

여러분이 만든 컴포넌트들을 주방에서 재료를 모아 맛있는 요리를 조리하는 요리사라고 상상해 보세요. 이 시나리오에서 React는 고객의 주문을 받고 요리를 서빙하는 웨이터 역할을 합니다. UI를 요청하고 제공하는 이 과정은 다음 세 가지 단계로 이루어집니다.

  1. 렌더링 트리거 (Triggering) : 손님의 주문을 주방으로 전달하기
  2. 컴포넌트 렌더링 (Rendering) : 주방에서 주문받은 요리 준비하기
  3. DOM에 커밋 (Committing) : 완성된 요리를 손님 테이블에 올리기

👨‍🏫 강사의 보충 설명:
React가 왜 이런 방식을 취할까요? 바로 '효율성' 때문입니다. 매번 화면을 직접 지우고 새로 그리는 대신, 가상의 공간(Virtual DOM)에서 먼저 변경 사항을 요리(계산)해 본 다음, 정말 필요한 부분만 실제 화면(DOM)에 반영(서빙)하기 위해서죠!

React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
트리거 (Trigger)

The Card Chef gives React a fresh Card component.
렌더링 (Render)

React delivers the Card to the user at their table.
커밋 (Commit)


1단계: 렌더링 트리거 (Trigger a render)

컴포넌트가 렌더링을 시작하는 데에는 두 가지 이유가 있습니다.

  1. 컴포넌트의 초기 렌더링 (Initial render) 일 때.
  2. 컴포넌트(혹은 상위 부모 컴포넌트)의 상태(state)가 업데이트 되었을 때.

초기 렌더링 (Initial render)

여러분의 앱이 처음 시작될 때, 초기 렌더링을 트리거(발생)해야 합니다. 프레임워크(Next.js 등)나 샌드박스 환경에서는 종종 이 코드가 숨겨져 있기도 하지만, 기본적으로 대상이 되는 DOM 노드를 가지고 createRoot를 호출한 다음, 여러분의 컴포넌트와 함께 render 메서드를 호출하는 방식으로 이루어집니다.

// src/index.js
import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);
// src/Image.js
export default function Image() {
  return (
    <img
      src="[https://i.imgur.com/ZF6s192.jpg](https://i.imgur.com/ZF6s192.jpg)"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

한번 여러분의 코드에서 root.render() 호출 부분을 주석 처리해 보세요. 화면에서 컴포넌트가 싹 사라지는 것을 볼 수 있을 거예요! 즉, 이 코드가 앱의 시작점 역할을 합니다.

상태 업데이트 시 리렌더링 (Re-renders when state updates)

컴포넌트가 초기에 렌더링되고 나면, set 함수를 통해 상태(state)를 업데이트하여 추가적인 렌더링(리렌더링)을 트리거할 수 있습니다. 컴포넌트의 상태를 업데이트하면 렌더링이 자동으로 대기열(queue)에 추가됩니다.

(식당 손님이 처음 주문을 한 이후에, 목이 마르거나 배가 고파지는 상태 변화에 따라 차, 디저트, 기타 여러 가지를 추가로 주문하는 것과 같다고 상상해 보세요!)

React as a server in a restaurant, serving a Card UI to the user, represented as a patron with a cursor for their head. The patron expresses they want a pink card, not a black one!
상태 업데이트...

React returns to the Component Kitchen and tells the Card Chef they need a pink Card.
...트리거...

The Card Chef gives React the pink Card.
...렌더링!


2단계: React가 컴포넌트를 렌더링합니다 (React renders your components)

여러분이 렌더링을 트리거하고 나면, React는 화면에 무엇을 표시해야 할지 알아내기 위해 여러분의 컴포넌트들을 호출합니다. 즉, "렌더링"이란 React가 여러분의 컴포넌트(함수)를 호출하는 것을 의미합니다.

  • 초기 렌더링 시: React는 최상위 루트(root) 컴포넌트를 호출합니다.
  • 이후의 렌더링(리렌더링) 시: React는 상태 업데이트가 일어나 렌더링을 트리거한 바로 그 함수 컴포넌트를 호출합니다.

이 과정은 재귀적(recursive)으로 일어납니다. 업데이트된 컴포넌트가 내부에서 다른 컴포넌트를 반환하면, React는 다음으로 컴포넌트를 렌더링하고, 그 컴포넌트가 또 다른 것을 반환하면 그다음 컴포넌트를 렌더링하는 식입니다. 더 이상 중첩된 컴포넌트가 없고, 화면에 표시되어야 할 내용을 React가 정확히 알게 될 때까지 이 과정은 계속됩니다.

👨‍🏫 강사의 보충 설명:
이 단계에서 React는 실제 브라우저 화면(DOM)을 건드리지 않아요! 오직 메모리 상에서 자신이 만든 가상의 구조(Virtual DOM)만을 그려보며 어떤 변화가 필요한지 '계획'하는 단계랍니다.

다음 예시에서 React는 Gallery() 함수와 Image() 함수를 여러 번 호출하게 됩니다.

// src/Gallery.js
export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="[https://i.imgur.com/ZF6s192.jpg](https://i.imgur.com/ZF6s192.jpg)"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}
// src/index.js
import Gallery from './Gallery.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Gallery />);
img { margin: 0 10px 10px 0; }
  • 초기 렌더링 동안, React는 <section>, <h1>, 그리고 3개의 <img> 태그를 위한 DOM 노드들을 생성합니다.
  • 리렌더링 동안, React는 이전 렌더링 이후 변경된 속성(properties)이 있는지, 있다면 어떤 것인지 계산합니다. 하지만 다음 단계인 커밋(commit) 단계가 되기 전까지는 이 계산된 정보를 가지고 아무런 동작도 하지 않습니다.

⚠️ 함정 주의 (Pitfall)

렌더링은 반드시 순수한 계산(pure calculation)이어야만 합니다.

  • 동일한 입력, 동일한 출력: 같은 입력(props, state 등)이 주어지면, 컴포넌트는 항상 같은 JSX를 반환해야 합니다. (토마토 샐러드를 주문한 손님에게 양파 샐러드를 서빙하면 안 되겠죠!)
  • 자기 일에만 집중하기: 렌더링 전에 존재하던 객체나 변수를 변경해서는 안 됩니다. 즉, 부작용(Side Effect)이 없어야 합니다. (한 사람의 주문이 다른 사람의 주문을 변경해서는 안 됩니다.)

그렇지 않으면 코드베이스가 복잡해지면서 매우 혼란스러운 버그나 예측할 수 없는 동작에 직면할 수 있습니다. "Strict Mode(엄격 모드)"로 개발할 때, React는 각 컴포넌트 함수를 두 번씩 호출하는데, 이는 순수하지 않은 함수로 인해 발생한 실수를 표면으로 드러내는 데 큰 도움을 줍니다.

🧐 깊게 파보기 (Deep Dive): 성능 최적화

업데이트된 컴포넌트 안에 중첩된 모든 컴포넌트를 렌더링하는 기본 동작 방식은, 업데이트된 컴포넌트가 트리 구조상 너무 높은 곳에 위치할 경우 성능 측면에서 최적이 아닐 수 있습니다. 만약 성능 문제에 부딪히게 된다면, Performance 섹션에 설명된 여러 가지 선택적 최적화 방법들을 사용할 수 있습니다. 하지만 미리 설레발치며 너무 이르게 최적화(premature optimization)를 하지는 마세요! 대부분의 경우 기본 동작으로도 충분히 빠릅니다.


3단계: React가 DOM에 변경사항을 커밋합니다 (React commits changes to the DOM)

컴포넌트를 렌더링(호출)하고 나면, 비로소 React는 실제 DOM을 수정합니다.

  • 초기 렌더링의 경우, React는 appendChild() DOM API를 사용하여 자신이 생성한 모든 DOM 노드들을 화면에 배치합니다.
  • 리렌더링의 경우, React는 DOM이 최신 렌더링 출력 결과와 일치하도록 최소한의 필수적인 작업들(렌더링 단계에서 이미 계산해둔 것들이죠!)만을 적용합니다.

React는 렌더링 결과 간에 차이가 있을 때만 DOM 노드를 변경합니다. 예를 들어, 1초마다 부모로부터 다른 props를 전달받아 리렌더링되는 컴포넌트가 있다고 해봅시다. 이 컴포넌트의 <input> 에 텍스트를 입력해서 value를 업데이트해 보세요. 컴포넌트가 계속 리렌더링됨에도 불구하고 입력한 텍스트가 사라지지 않는 것을 볼 수 있습니다.

// src/Clock.js
export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}
// src/App.js
import { useState, useEffect } from 'react';
import Clock from './Clock.js';

function useTime() {
  const [time, setTime] = useState(() => new Date());
  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return time;
}

export default function App() {
  const time = useTime();
  return (
    <Clock time={time.toLocaleTimeString()} />
  );
}

👨‍🏫 강사의 보충 설명:
이 과정이 바로 React의 마법 중 하나인 '재조정(Reconciliation)'입니다. React는 이전 렌더링 결과와 현재 렌더링 결과를 비교해서 '달라진 부분'만 콕 집어서 실제 DOM에 업데이트해요.

이 예제가 정상적으로 동작하는 이유는 마지막 커밋 단계에서 React가 새로운 time 값으로 <h1>의 내용만 업데이트하기 때문입니다. React는 <input>이 지난번 렌더링과 동일한 위치에 그대로 있다는 것을 확인하고, <input> 요소나 그 안의 value 값을 건드리지 않고 그대로 내버려 둡니다.


에필로그: 브라우저 페인트 (Browser paint)

렌더링이 완료되고 React가 DOM을 업데이트하고 나면, 브라우저는 화면을 다시 그립니다(repaint). 이 과정은 웹 개발 세계에서 보통 "브라우저 렌더링(browser rendering)"이라고 알려져 있지만, React의 "렌더링"과 혼동하는 것을 막기 위해 공식 문서 전반에 걸쳐 이 과정을 "페인팅(painting)" 이라고 부를 것입니다.

👨‍🏫 강사의 보충 설명:
브라우저가 화면을 그릴 때는 스타일 계산, 레이아웃(위치와 크기 잡기), 페인트(실제 픽셀 채우기), 합성(레이어 합치기) 등의 과정을 거쳐요. React의 커밋 단계가 끝나면, 브라우저가 이 작업들을 넘겨받아 우리가 보는 최종 화면을 모니터에 뿌려주는 것이죠!

A browser painting 'still life with card element'.


📝 요약 (Recap)

  • React 앱에서 일어나는 모든 화면 업데이트는 세 단계를 거칩니다.
    1. 트리거 (Trigger)
    2. 렌더링 (Render)
    3. 커밋 (Commit)
  • 컴포넌트 내부의 실수를 찾기 위해 Strict Mode(엄격 모드)를 유용하게 사용할 수 있습니다.
  • 렌더링 결과가 지난번과 동일하다면, React는 실제 DOM을 전혀 건드리지 않습니다.

사이트맵 (Sitemap)

모든 문서 페이지 개요 보기 (Overview of all docs pages)

profile
프론트에_가까운_풀스택_개발자

0개의 댓글