Mini React
Virtual DOM과 Hooks를 직접 구현하고, 동기식 렌더링을 작업 단위로 나누는 Fiber Reconciler와 협력형 스케줄러로 확장한 JavaScript 런타임
프로젝트 요약
담당 역할 및 기여
- Virtual DOM·Diff/Patch·Hooks의 실행 흐름 구현
- Fiber Reconciler와 협력형 스케줄러 설계 및 검증
핵심 구현
- Render·Commit 분리와 오래된 비동기 렌더 취소
- key 기반 DOM 재사용과 작업 중단·재개 구현
한 번에 처리하던 렌더링을 작은 작업 단위로 분리
상태 변경이 화면에 반영되는 과정을 이해하기 위해 Virtual DOM과 Hooks를 직접 구현했습니다. 초기 Diff/Patch 엔진은 전체 트리를 재귀적으로 비교해 한 번에 반영하는 구조여서, 계산 도중 브라우저에 제어권을 돌려주거나 새로운 렌더 요청으로 교체하기 어려웠습니다.
이를 해결하기 위해 트리 순회를 Fiber 단위로 나누고, 변경 사항을 계산하는 Render와 실제 DOM을 바꾸는 Commit을 분리했습니다.
- 재귀 호출로 전체 트리 비교
- 계산 중간에 중단·재개 불가
- Diff 직후 DOM에 변경 반영
- Fiber 단위로 다음 작업 추적
- 작업 양보·재개·취소 가능
- Render 완료 후 Commit
Current · WIP Fiber
현재 화면의 Current 트리는 유지하고, 다음 화면은 Work-in-Progress 트리에서 계산합니다. parent·child·sibling으로 다음 작업을 찾고, alternate로 이전 Fiber를 연결해 DOM을 재사용합니다.
브라우저에 제어권을 양보하고, 최신 요청만 화면에 반영
patchAsync()는 requestIdleCallback과 timeout fallback을 사용해 렌더 작업을 여러 구간으로 나눕니다. 실행을 멈춘 위치를 저장하고 다음 구간에서 이어가며, 새 요청이 들어오면 아직 Commit하지 않은 이전 작업을 취소합니다. 기존 patch()는 같은 Fiber 작업을 한 번에 끝내도록 해 동기 동작을 유지했습니다.
patchAsync()는 브라우저에 제어권을 돌려주며 Render를 이어가고, Commit은 완성된 결과를 한 번에 반영합니다.계산 중에는 현재 화면을 유지하고, 끝까지 완성된 작업의 변경 사항만 DOM에 반영하도록 했습니다.
Hooks · 업데이트를 묶는 시점
Microtask batching으로 여러 setState 요청을 하나의 업데이트로 묶습니다.
Scheduler · 렌더 작업을 나누는 방식
시작된 렌더를 작은 작업으로 나누고, 브라우저에 제어권을 돌려준 뒤 이어서 처리합니다.
리스트 순서가 바뀌어도 기존 DOM을 재사용
Keyed Reconciliation에서는 같은 key와 type을 가진 항목을 동일한 노드로 판단합니다. 이전 Fiber와 DOM을 재사용하고, 위치가 달라진 노드에는 MOVE Effect를 기록해 새로 생성하지 않고 이동으로 처리했습니다.
목록의 순서 변경과 노드의 생성·삭제를 구분해 필요한 DOM 변경만 반영했습니다.
83개의 테스트로 실행 흐름과 DOM 반영 시점을 검증
기존 Hooks·Diff/Patch·History 테스트에 Fiber 시나리오를 추가해 전체 83개의 테스트를 통과했습니다. 최종 화면뿐 아니라, 계산 중 DOM 유지와 작업 취소·재개 과정도 확인했습니다.
| 검증 항목 | 확인한 동작 |
|---|---|
| Fiber 연결 | 부모·자식·형제와 이전 Fiber의 연결을 유지 |
| Render / Commit | 계산이 완료되기 전에는 실제 DOM을 변경하지 않음 |
| Yield / Resume | 중단한 위치를 보존하고 다음 작업 구간에서 재개 |
| Key 기반 이동 | 리스트 순서가 바뀌어도 기존 DOM 노드를 재사용 |
| 오래된 작업 취소 | 이전 비동기 렌더를 취소하고 최신 작업만 Commit |