탐색으로 돌아가기
React20 / 24 단계

React Render Phase

Virtual DOM · Diffing

렌더 단계에서 React는 새 Virtual DOM을 만들고 이전 것과 비교(재조정)해 무엇이 바뀌었는지 계산한다. 부작용 없이 순수하게.

렌더 단계(Render Phase)는 React가 상태나 props 변경을 감지한 뒤 각 컴포넌트 함수를 호출해 새 UI의 청사진을 계산하는 단계다. JSX는 빌드 타임에 컴파일러(Babel/SWC/tsc)에 의해 react/jsx-runtime의 jsx()/jsxs() 호출(React 17+의 기본 automatic runtime, 개발 모드는 jsxDEV())로 변환되고 — 구형 classic runtime은 React.createElement(type, props, ...children)로 변환했다 — 이 호출들은 실제 DOM이 아니라 순수 JavaScript 객체 트리인 Virtual DOM(엘리먼트 트리)을 만든다. React는 이 새 트리를 이전 렌더 결과 트리와 비교(diffing)하는 재조정(reconciliation)을 수행하는데, 일반적인 트리 비교는 O(n^3)이지만 React는 두 가지 휴리스틱 가정(서로 다른 타입은 다른 트리를 만든다, key로 안정적인 자식을 힌트한다)에 기반해 O(n) 알고리즘으로 근사한다. 타입이 다르면 기존 서브트리를 통째로 파기하고 새로 만들며(상태 전부 손실), 같은 타입이면 DOM 노드를 재사용하고 바뀐 속성만 갱신한다. 리스트 자식은 key를 통해 위치가 아닌 정체성(identity)으로 매칭해 이동/삽입/삭제를 효율적으로 처리한다. 이 단계는 반드시 순수(pure)해야 하며, 부작용 없이 중단·재개·폐기될 수 있어야 하므로 렌더 중 DOM 조작·구독·타이머 등은 금지된다.

내부 구성

컴포넌트 함수 (Component Function)
props를 입력받아 엘리먼트 트리를 반환하는 순수 함수. 렌더 단계에서 React가 호출한다
JSX → jsx() / createElement
JSX 문법이 컴파일러(Babel/SWC/tsc)에 의해 react/jsx-runtime의 jsx()/jsxs() 호출(React 17+ automatic runtime; 구형은 React.createElement)로 변환되는 트랜스파일 산출물
React 엘리먼트 (React Element)
{ type, key, ref, props } 형태의 불변 순수 객체. 화면에 무엇을 그릴지 기술하는 최소 단위(Virtual DOM의 노드)
Virtual DOM 트리 (Element Tree)
엘리먼트들이 이루는 인메모리 트리. 실제 DOM의 가볍고 비용 없는 표현으로 diff의 비교 대상이 된다
재조정자 (Reconciler)
이전 트리와 새 트리를 비교해 어떤 노드를 생성/갱신/삭제할지 결정하는 알고리즘 엔진
타입 비교 규칙 (Type Comparison)
동일 위치의 두 엘리먼트 type을 === 로 비교. 다르면 서브트리 재생성, 같으면 재사용
key 매칭 (Keyed Reconciliation)
리스트 자식을 위치가 아닌 key 기준으로 매칭해 이동/삽입/삭제를 최소화하고 상태를 보존
순수성 제약 (Purity Constraint)
렌더 함수는 부작용이 없어야 한다는 규칙. 이 덕분에 작업을 안전하게 중단·재개·폐기 가능

핵심 포인트

  • JSX는 컴파일러에 의해 jsx()/jsxs()(React 17+ automatic runtime; 구형은 React.createElement) 호출로 변환되어 실제 DOM이 아닌 순수 JS 객체(Virtual DOM 엘리먼트)를 생성한다
  • 재조정(reconciliation)은 새 엘리먼트 트리와 이전 트리를 diffing해 최소 변경 집합을 계산하는 과정이다
  • 일반 트리 diff는 O(n^3)이지만 React는 휴리스틱으로 O(n)에 근사한다(가정 2개: 타입 다르면 다른 트리, key로 안정적 자식 힌트)
  • 타입 비교: <div>→<span>처럼 타입이 바뀌면 노드 파기 후 재생성(자식과 상태 전부 손실), 같은 타입이면 노드 재사용+변경 속성만 패치
  • key는 형제 간에만 유일하면 되며, 배열 index를 key로 쓰면 재정렬 시 상태 꼬임·불필요한 언마운트가 발생한다
  • 렌더 단계는 순수해야 한다: 같은 입력→같은 출력, 부작용 금지, 그래서 중단/재개/폐기가 안전하다
  • 이 단계는 계산만 할 뿐 실제 DOM은 건드리지 않는다(실제 반영은 커밋 단계)

심화

면접 단골 오해 하나: Virtual DOM은 '빠르기 때문에' 쓰는 게 아니라 '선언적 프로그래밍 모델을 가능케 하는 추상화'다. 직접 DOM을 명령형으로 조작하는 것보다 순수 함수로 매 렌더 전체 UI를 새로 그려내고 diff로 필요한 부분만 반영하는 편이, 정확도와 유지보수성 측면에서 압도적으로 유리하다. 극단적으로 최적화된 수제 명령형 코드가 특정 케이스에서 더 빠를 수 있으나 그런 코드는 인간이 유지보수하기 어렵다. 또 하나, diff는 '같은 위치(same position in tree)'에서만 이루어진다는 점이 중요하다. 조건부 렌더링에서 <A/>를 <B/>로 바꾸면 위치는 같아도 타입이 달라 A의 상태가 전부 날아가고, 반대로 같은 타입 컴포넌트를 위치만 유지하면 key를 바꾸지 않는 한 상태가 남는다. "컴포넌트를 조건부로 다른 위치에서 렌더하면 상태가 사라지는 이유", "key를 바꿔 강제로 리마운트하는 기법"은 이 위치 기반 diff에서 직접 파생되는 실전 지식이다. React 19에서 이 재조정 엔진은 여전히 Fiber 위에서 동작하며, key 기반 diff의 O(n) 특성과 위치 기반 규칙은 변하지 않았다.

쉽게 말하면 새 설계도(가상 DOM)를 그리고 기존 설계도와 겹쳐 바뀐 부분만 표시하는 것. 아직 공사는 안 함.

면접 예상 질문

#Virtual DOM#Reconciliation#Diffing#Render Phase