Skip to content

React 렌더링과 커밋

개요

"렌더링" 하면 화면에 그리는 걸 떠올리기 쉬운데요, React에선 뜻이 조금 달라요. React가 화면에 뭔가를 보여주기 전에는 반드시 컴포넌트를 "렌더"해야 하죠. 이 과정이 어떻게 돌아가는지 알면 코드가 언제 실행되고 화면이 왜 변하는지 설명할 수 있어요.

핵심 개념

화면을 갱신하는 일은 공식 문서의 레스토랑 비유를 빌려 세 단계로 나눠요.

  1. 렌더 트리거(Trigger) — 렌더를 일으키는 두 가지 이유가 있어요. 앱이 처음 뜰 때의 초기 렌더와, 상태(set 함수)가 갱신돼서 다시 그려지는 재렌더죠. 초기 렌더는 createRoot(...).render(<App/>) 호출로 시작돼요.
  2. 컴포넌트 렌더(Render) — 렌더링은 곧 "React가 여러분의 컴포넌트 함수를 호출하는 것"이에요. 초기 렌더에는 루트 컴포넌트를, 재렌더에는 상태가 바뀐 컴포넌트를 호출하고, 그 함수가 다른 컴포넌트를 반환하면 그 컴포넌트도 호출하며 재귀적으로 끝까지 내려가요.
  3. DOM에 커밋(Commit) — 렌더 결과를 바탕으로 React가 DOM을 바꿔요. 초기 렌더는 만든 DOM 노드를 전부 붙이고, 재렌더는 이전 렌더와 달라진 부분에만 최소한의 변경을 가해요.
import { createRoot } from 'react-dom/client';

// 앱 시작 시 초기 렌더를 트리거해요.
const root = createRoot(document.getElementById('root'));
root.render(<App />);

여기서 핵심은 마지막 단계예요. React는 이전 렌더와 결과가 다를 때만 DOM을 건드려요. 예를 들어 매초 바뀌는 시계 컴포넌트에서 <h1>{time}</h1>만 값이 바뀌면 그 노드만 갱신하고, 같은 자리에 있는 <input>은 손도 대지 않죠. 그래서 입력 중인 텍스트가 재렌더 때 사라지지 않아요.

렌더는 반드시 순수 계산이어야 해요. 같은 입력이면 같은 JSX를 반환하고, 렌더 중에 밖의 객체나 변수를 바꾸지 말아야 해요. 그렇지 않으면 코드베이스가 커질수록 알 수 없는 버그가 생겨요. 브라우저가 실제로 화면을 다시 칠하는 "페인트"는 이 커밋 뒤에 일어나는데, React 문서는 이걸 "렌더링"이라 부르지 않고 따로 부릅니다.

실제 적용(데이터스케쳐스 관점)

캔버스처럼 수백 개 컴포넌트가 얽힌 화면에서 "언제 다시 그려지는가"는 성능의 정면 문제예요. 상태 업데이트 하나가 렌더를 트리거하면 React가 그 컴포넌트 아래의 모든 중첩 컴포넌트를 다시 호출해요. 기본 동작이므로, 트리 상단에서 자주 상태가 바뀌면 아래가 통째로 다시 렌더될 수 있어요.

그래서 데이터스케쳐스에서는 화면 데이터의 형태를 정리해서 "바뀌어야 하는 부분"을 최소로 좁히는 걸 신경 써요. 렌더는 순수해야 한다는 원칙은 여기서 특히 중요해요. 렌더 중에 문서 상태를 변형하면 Strict Mode가 아니라면 눈치채기 어렵고, 재렌더 횟수가 늘수록 같은 버그가 반복돼요. 성능 문제가 생기기 전에 미리 최적화하기보다, 먼저 상태 구조와 렌더 트리를 단순하게 유지하는 게 공식 문서의 조언이기도 해요.

더 알아보기