파트 1 — Stack Reconciler 시대
Source: Dev.to
The High-Level Process
Deep Diving into the Reconciliation Process
1. The Trigger: this.setState()
this.setState() 은 “시작” 신호입니다. React는 즉시 해당 컴포넌트와 그 전체 서브트리에서 무엇이 변했는지 파악하는 과정을 시작합니다.
2. The Recursive “Dive”
React는 컴포넌트의 render() 메서드를 호출합니다. 컴포넌트가 중첩되어 있기 때문에 재귀적인 체인이 발생합니다:
- 컴포넌트 A에 자식 컴포넌트 B가 있으면, React는
A.render()를 호출한 뒤 바로 아래로 내려가B.render()를 호출합니다. - 이러한 함수 호출들은 모두 JavaScript 호출 스택에 쌓입니다.
핵심 제약: JavaScript는 단일 스레드입니다. 호출 스택이 React의 재귀에 의해 점유되어 있는 동안, 브라우저는 스크롤이나 “Cancel” 버튼 클릭 같은 사용자 입력을 처리할 수 없습니다. 메인 스레드는 스택이 완전히 비워질 때까지 “인질” 상태가 됩니다.
3. Generating the Virtual DOM
React는 메모리 상의 가벼운 JavaScript 객체, 즉 현재 시점에 UI가 어떠해야 하는지를 나타내는 Virtual DOM을 생성합니다.
4. Synchronous Diffing & Immediate DOM Patching
Diffing(차이점 찾기)과 patching(화면 업데이트)은 동시에 이루어집니다. 알고리즘이 트리를 순회하면서 차이를 발견하면—예를 들어 가 로 바뀌는 경우—즉시 document.setAttribute 나 appendChild 를 호출해 실제 DOM을 바로 업데이트합니다.
React가 트리의 나머지를 계산하는 도중에도 실제 DOM을 변형하기 때문에, 이 과정은 중단될 수 없습니다. UI가 이미 변하기 시작했기 때문에 중간에 멈출 수 없습니다.
The Performance Bottleneck
Jank and Dropped Frames
부드러운 60 fps를 유지하려면 브라우저는 한 프레임을 그리는데 약 16.6 ms가 필요합니다. 만약 reconciliation이 30 ms가 걸린다면, 브라우저는 프레임을 건너뛰게 되고 눈에 보이는 끊김(“jank”)이 발생합니다.
Delayed User Interactions
사용자가 큰 리스트를 reconciliation하는 동안 입력 필드에 타이핑을 하면, 키 입력이 화면에 표시되는 시점은 전체 트리 계산이 끝날 때까지 지연됩니다.
The Realization
동시 diffing과 patching은 복잡하고 고도로 인터랙티브한 애플리케이션에 한계가 있음을 보여주었습니다. 앞으로 나아가기 위해 두 가지 목표가 설정되었습니다:
- “수학”(diffing)과 “쓰기”(patching)를 분리한다.
- 일시 정지 가능하게 만든다: 사용자가 더 중요한 동작을 수행했을 때 “수학”을 멈출 수 있는 능력.
이 깨달음은 React 역사상 가장 야심찬 재작성, Fiber의 서막을 열었습니다. Part 2에서는 Fiber가 어떻게 React 내부를 완전히 재설계해 웹을 그 어느 때보다 부드럽게 만들었는지 깊이 파헤칠 예정입니다.