개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Reconciliation — 변경된 UI를 계산하는 과정

2026년 5월 7일
ReactReconciliationDiffingKey

Reconciliation이란 무엇인가

Reconciliation은 React가 컴포넌트의 State나 Props가 변경되었을 때 어떤 DOM 조작이 필요한지 결정하는 과정입니다. React는 이전 렌더링 결과(Virtual DOM)와 새 렌더링 결과를 비교하고, 그 차이만큼만 실제 DOM에 반영합니다.

이 과정이 있기에 개발자는 매번 DOM을 직접 조작하지 않고 "이 상태일 때 UI는 이래야 한다"고 선언하기만 하면 됩니다.

Diff 알고리즘

두 트리를 완전히 비교하는 일반 알고리즘은 O(n³)의 시간 복잡도를 가집니다. 1000개의 노드가 있으면 10억 번의 비교가 필요합니다. React는 실용적인 두 가지 가정을 통해 이를 O(n)으로 줄였습니다.

가정 1: 타입(type)이 다른 두 엘리먼트는 다른 트리를 생성한다.

같은 위치에서 엘리먼트 타입이 변경되면(div → article) React는 해당 서브트리 전체를 버리고 새로 마운트합니다. 기존 컴포넌트 인스턴스는 언마운트되고 State도 초기화됩니다.

// 이전
<div>
  <Counter />
</div>

// 새로운 — Counter가 언마운트 후 재마운트됨
<article>
  <Counter />
</article>

같은 타입이면 속성(props)만 비교하여 달라진 속성만 업데이트합니다.

// 이전
<div className="old" style={{ color: 'red' }} />

// 새로운 — className만 DOM에서 업데이트됨
<div className="new" style={{ color: 'red' }} />

가정 2: 개발자는 key prop으로 자식 엘리먼트가 렌더링 간에 안정적임을 힌트로 줄 수 있다.

트리 비교 방식

React는 트리를 루트에서 시작하여 깊이 우선(Depth-First)으로 동일한 위치의 엘리먼트를 비교합니다.

이전 트리           새 트리
    A                   A
   / \                 / \
  B   C               B   D   ← C → D: 타입 변경
 / \                 /
E   F               E

위 예시에서 C가 D로 변경되면 C와 그 자식(있다면)은 모두 언마운트되고 D가 새로 마운트됩니다. B는 타입이 같으므로 props만 비교합니다.

Key의 역할

자식 목록을 렌더링할 때 key가 없으면 React는 인덱스를 기준으로 비교합니다. 목록 중간에 항목이 삽입되면 이후 모든 항목이 "변경된" 것으로 처리되어 불필요한 DOM 업데이트가 발생합니다.

// key 없음: 목록 앞에 항목 삽입 시 모든 항목 재렌더링
['B', 'C'].map(item => <li>{item}</li>)
// → 'A' 삽입 후 ['A', 'B', 'C'] → B, C 모두 업데이트 처리됨

key를 지정하면 React가 각 항목을 개별적으로 추적합니다. 위치가 바뀌어도 같은 key를 가진 엘리먼트는 동일한 컴포넌트로 인식합니다.

// key 있음: 각 항목을 개별 추적
items.map(item => <li key={item.id}>{item.name}</li>)
// → 새 항목만 마운트, 기존 항목은 State 유지

key는 형제 엘리먼트 사이에서만 고유하면 됩니다. 전역적으로 고유할 필요는 없습니다.

Key 선택 기준

key로 사용할 값을 선택할 때는 다음 기준을 따릅니다.

  • 데이터베이스 ID: 가장 좋은 선택입니다. 항목의 실제 고유 식별자를 사용합니다.
  • 안정적인 값: 렌더링 간에 변하지 않아야 합니다.
  • 인덱스: 목록이 재정렬되지 않고 항목이 추가/삭제되지 않을 때만 사용합니다.
// 나쁜 예: 랜덤값은 매 렌더링마다 바뀜 → 모든 항목 재마운트
items.map(item => <Item key={Math.random()} {...item} />)

// 나쁜 예: 인덱스는 재정렬 시 의도치 않은 State 이동 발생
items.map((item, index) => <Item key={index} {...item} />)

// 좋은 예: 데이터 고유 ID 사용
items.map(item => <Item key={item.id} {...item} />)

Reconciliation과 State 보존

Reconciliation에서 중요한 부수 효과는 State 보존입니다. 같은 위치에 같은 타입(또는 같은 key)의 컴포넌트가 유지되면 그 컴포넌트의 State도 유지됩니다. 반대로 컴포넌트가 언마운트되면 State는 사라집니다.

// isA가 true→false로 바뀌면 Counter가 언마운트 후 재마운트 → count 초기화
{isA ? <Counter /> : <div><Counter /></div>}

// 같은 위치에 같은 타입 유지 → count 보존
{isA ? <Counter color="red" /> : <Counter color="blue" />}

이 동작을 이해하면 의도치 않은 State 초기화나 State 보존 문제를 예방할 수 있습니다.

정리

Reconciliation은 React가 선언형 프로그래밍 모델을 유지하면서도 효율적으로 DOM을 갱신하는 핵심 메커니즘입니다. 타입 기반 비교와 key를 통한 안정적 추적이 O(n) Diffing을 가능하게 하며, key를 올바르게 사용하는 것이 목록 성능과 State 관리의 예측 가능성에 직접적인 영향을 줍니다.

← 이전 글Rendering — React가 UI를 계산하여 화면에 반영하는 과정
다음 글 →Virtual DOM — 변경 사항을 효율적으로 계산하는 방법