React는 사용자에게 화면을 보여주기 전에 총 3개의 단계를 걸쳐 작업을 처리한다. 각각은 Triggering, Rendering, Committing phase이며 Commit phase가 끝나야 비로소 사용자는 모니터에서 화면을 볼 수 있게 된다.
<script>
// 초기 렌더링
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// 상태 업데이트
const [count, setCount] = useState(0);
setCount(count + 1); // 🔥 트리거!
// Context 값 변경
const ThemeContext = React.createContext();
const [theme, setTheme] = useState('dark');
setTheme('light'); // 🔥 트리거!
</script>
<script>
function CommitPhaseExample() {
const [count, setCount] = useState(0);
// Before Mutation
// DOM 변경 전 실행
const prevCount = useRef();
// Mutation
// 실제 DOM 변경이 일어나는 시점
// Layout
// DOM 변경 후 실행
useLayoutEffect(() => {
prevCount.current = count;
}, [count]);
return <div>Count: {count}</div>
}
</script>
<script>
// 실제 Fiber 노드의 주요 속성들
const fiberNode = {
// 🏷️ 컴포넌트 정보
type: 'div', // HTML 태그 or 컴포넌트 함수
key: 'unique-key', // React key
elementType: 'div' // 원본 타입
// 🌳 트리 구조 (Linked List로 구현)
child: null, // 첫 번째 자식
sibling: null, // 다음 형제
return: null, // 부모 (parent가 아닌 reture!)
// 📦 데이터
memoizedProps: {}, // 이전 props
pendingProps: {}, // 새로운 props
memoizedState: {}, // 이전 state (Hook 체인)
// 🔄 업데이트
updateQueue: null, // 상태 업데이트 큐
// 🚩 작업 표시
flags: 0, // 이 노드의 side effect
subtreeFlags: 0, // 하위 트리의 side effect
// 🔗 Fiber 아키텍처
alternate: null, // 다른 트리의 대응 노드
lanes: 0, // 우선순위 (Concurrent Features)
}
</script>
<script>
// 기존 방식 (React 15): 재귀적 순회 - 중단 불가
function walkTreeRecursive(element) {
doWork(element);
element.children.forEach(child => {
walkTreeRecursive(child); // 🚫 중단할 수 없음
});
}
// Fiber 방식: 반복적 순회 - 중단 가능
function walkTreeIterative(fiber) {
while (fiber) {
doWork(fiber);
// ⏸️ 여기서 중단 가능!
if (shouldYield()) {
return fiber; // 나중에 여기서 재시작
}
fiber = getNextFiber(fiber);
}
}
</script>
<script>
// 지금 화면에 보이는 UI
const currentTree = {
// ✅ 실제 DOM에 반영된 안정적인 상태
// ✅ 사용자가 보고 있는 UI
// ✅ 이전 렌더링의 결과물
};
</script>
<script>
// 다음에 보여질 UI (작업 중)
const workInProgressTree = {
// 🚧 새로운 state와 props를 반영 중
// 🚧 render phase에서 구성되는 트리
// 🚧 commit 후 current tree가 될 예정
};
</script>
<script>
function updateProcess() {
// 1️⃣ 업데이트 시작: current 복제 → workInProgress 생성
let workInProgress = createWorkInProgress(current);
// 2️⃣ Render Phase: workInProgress 트리 구성
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
// 3️⃣ Commit Phase: 트리 교체 (포인터 스왑)
root.current = finishedWork; // 🔄 단순한 포인터 교체!
}
</script>
<script>
// Before
<div>
<Counter />
</div>
// After
<span> {/* 🔥 타입이 변경됨! */}
<Counter />
</span>
// 결과: div와 Counter 모두 완전히 새로 생성 (언마운트 → 마운트)
</script>
<script>
// ❌ key 없음: 비효율적
{items.map(item =>
<TodoItem data={item} /> // 순서 변경시 모든 항목 재생성
)}
// ✅ key 있음: 효율적
{items.map(item =>
<TodoItem key={item.id} data={item} /> // 재정렬만 발생
)}
</script>
<script>
// Before
<div className="old">Hello</div>
// After
<div className="new">Hi</div>
// 결과: className만 업데이트, DOM 노드 재사용
</script>
<script>
function reconcileChildren(current, workInProgress, nextChildren) {
if (current === null) {
// 🆕 초기 렌더링: 새로운 자식들 마운트
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren
);
} else {
// 🔄 업데이트: 이전 자식들과 비교 후 재조정
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child, // 이전 자식
nextChildren // 새로운 자식
);
}
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
// 🎯 여기서 실제 diffing 알고리즘 실행
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild)
);
// ... 다른 타입들
}
}
if (Array.isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
// ... 더 많은 경우들
}
</script>
<script>
// React의 실제 플래그들
const Flags = {
NoFlags: 0b000000000000000000,
PerformedWork: 0b000000000000000001,
Placement: 0b000000000000000010, // 삽입
Update: 0b000000000000000100, // 업데이트
Deletion: 0b000000000000001000, // 삭제
ChildDeletion: 0b000000000000010000,
ContentReset: 0b000000000000100000,
Callback: 0b000000000001000000,
DidCapture: 0b000000000010000000,
Ref: 0b000000001000000000, // ref 변경
Snapshot: 0b000000010000000000, // getSnapshotBeforeUpdate
Passive: 0b000000100000000000, // useEffect
// ... 더 많은 플래그들
};
// 플래그 사용 예시
function markUpdate(fiber) {
fiber.flags |= Update; // 비트 OR 연산으로 플래그 추가
}
function hasUpdate(fiber) {
return (fiber.flags & Update) !== NoFlags; // 비트 AND로 플래그 확인
}
</script>
<script>
// 전체 업데이트 흐름
function fullUpdateCycle() {
// 1️⃣ TRIGGER PHASE
console.log('🔥 Trigger: 상태 업데이트 발생');
const update = createUpdate(newState); // setState 호출
enqueueUpdate(fiber, update); // queue에 등록
scheduleUpdateOnFiber(fiber); // 렌더링 스케쥴 등록
// 2️⃣ RENDER PHASE
console.log('🎨 Render: Virtual DOM 구성 시작');
function performWorkOnRoot(root) {
// Current 트리를 복제하여 WorkInProgress 생성
let workInProgress = createWorkInProgress(root.current);
// 작업 단위별로 처리 (중단 가능)
while (workInProgress !== null) {
try {
workInProgress = performUnitOfWork(workInProgress);
} catch (thrownValue) {
// 에러 처리
handleError(root, thrownValue);
}
}
return finishedWork;
}
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
// 🔄 Begin work: 컴포넌트 실행 & 재조정
let next = beginWork(current, unitOfWork);
if (next === null) {
// 🏁 Complete work: side effect 수집
next = completeUnitOfWork(unitOfWork);
}
return next;
}
// 3️⃣ COMMIT PHASE
console.log('✅ Commit: DOM 업데이트 시작');
function commitRoot(finishedWork) {
// 3-1: Before Mutation
console.log(' 📸 Before Mutation: 스냅샷 찍기');
commitBeforeMutationEffects(finishedWork);
// 3-2: Mutation
console.log(' 🔧 Mutation: DOM 실제 변경');
commitMutationEffects(finishedWork);
// 트리 교체 (핵심!)
root.current = finishedWork;
console.log(' 🔄 트리 교체 완료');
// 3-3: Layout
console.log(' 📐 Layout: 레이아웃 effect 실행');
commitLayoutEffects(finishedWork);
// 4: Passive (비동기)
scheduleCallback(() => {
console.log(' ⚡ Passive: useEffect 실행');
commitPassiveEffects(finishedWork);
});
}
}
</script>
<script>
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
증가
</button>
</div>
);
}
// 버튼 클릭시 발생하는 일들:
// 1️⃣ TRIGGER
// setCount(count + 1) 호출
// → 업데이트 큐에 새로운 상태 추가
// → 스케줄러에 작업 등록
// 2️⃣ RENDER
// Current Fiber Tree (count: 0)
// Counter Fiber
// ├── div Fiber
// ├── h1 Fiber (textContent: "0")
// └── button Fiber
//
// WorkInProgress Tree 구성 (count: 1)
// Counter Fiber (새로운 state: {count: 1})
// ├── div Fiber (변경 없음)
// ├── h1 Fiber (textContent: "1") ← 🔥 Update 플래그
// └── button Fiber (변경 없음)
// 3️⃣ COMMIT
// Before Mutation: (없음)
// Mutation: h1의 textContent를 "1"로 변경
// Layout: (없음)
// Passive: useEffect 실행 → document.title = "Count: 1"
</script>