React Fiber Architecture
TL;DR
Fiber는 React가 컴포넌트 트리를 표현하는 데이터 구조이자, 그 트리에 대한 work을 쪼개고 우선순위를 매기고 재개·폐기 가능한 단위로 만든 실행 모델 입니다. 핵심 6가지:
- Fiber 노드 = "한 컴포넌트 인스턴스에 대한 work 한 단위". React 16 이전의 stack-based reconciler를 대체하기 위해 만들어짐.
- 단방향 linked list tree (
return/child/sibling/index). 트리지만 메모리상으로는 linked list라 양방향 traverse가 자유로움. - Double buffer: 항상 두 트리(
current↔work-in-progress)가alternate포인터로 짝지어 있다. 새 트리를 만들면서도 현재 트리는 멀쩡. - Two-phase work:
Render phase(interrupt 가능,beginWork내려가며 +completeWork올라오며) +Commit phase(atomic, mutation → layout → passive). - Effects는 bitmask(
flags/subtreeFlags)로 fiber 자신에 기록되고,completeWork의bubbleProperties가 자식 → 부모로 OR-합산 → commit phase가 한 번에 traverse. - Hooks는 fiber 안에 singly linked list로 저장(
fiber.memoizedState). 그래서 hook 순서가 바뀌면 안 됨.
이 데이터 구조의 모양 때문에 — 그 위에서만 — Concurrent Rendering, Suspense, Error Boundary, Selective Hydration이 가능하다.
1. 왜 Fiber인가 — 16 이전의 한계
React 15까지의 reconciler는 "stack reconciler"라 불렸다. 그 모양은 단순했다:
function reconcile(node) {
// 1. 새 element와 비교
// 2. update 결정
for (const child of node.children) {
reconcile(child) // ← 재귀 호출
}
}
문제 3개:
- JS call stack을 사용 → 재귀가 끝나기 전엔 다른 작업이 메인 스레드 점유 불가
- 중단 불가 → 한 번 시작하면 끝까지 가야 함
- 우선순위 없음 → 모든 update가 동등하게 즉시 처리
이를 풀려면 reconciliation의 실행 상태를 JS call stack이 아닌 명시적 자료구조 에 담아야 한다. 그래야:
- 중단했다가 다음 frame에 이어서 진행
- 어떤 fiber까지 처리했는지 명시 (=
workInProgress포인터) - 더 급한 일이 들어오면 그 자료구조를 버리고 새로 시작
그 자료구조가 Fiber다. 이름의 유래는 "JS call stack 위의 가벼운 스레드(fiber)"라는 비유.
2. Fiber 노드의 데이터 구조
타입 정의는 ReactInternalTypes.js:89-210:
react-main/packages/react-reconciler/src/ReactInternalTypes.js:89-210
// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
export type Fiber = {
...
tag: WorkTag,
...
key: ReactKey,
...
elementType: any,
...
type: any,
...
stateNode: any,
...
return: Fiber | null,
// Singly Linked List Tree Structure.
child: Fiber | null,
sibling: Fiber | null,
index: number,
...
pendingProps: any,
memoizedProps: any,
updateQueue: mixed,
memoizedState: any,
dependencies: Dependencies | null,
...
mode: TypeOfMode,
// Effect
flags: Flags,
subtreeFlags: Flags,
deletions: Array<Fiber> | null,
lanes: Lanes,
childLanes: Lanes,
// This is a pooled version of a Fiber. Every fiber that gets updated will
// eventually have a pair. There are cases when we can clean up pairs to save
// memory if we need to.
alternate: Fiber | null,
...
};
필드 5개 그룹
| 그룹 | 필드 | 역할 |
|---|---|---|
| 정체성 | tag (FunctionComponent / HostComponent / Fragment 등 26종 enum), key, elementType, type, stateNode | 이 fiber가 무엇인가, 실제 DOM/instance는 어디 있나 |
| 트리 구조 | return, child, sibling, index | 부모/자식/형제 — singly linked tree |
| work 데이터 | pendingProps (들어오는 props), memoizedProps (직전 렌더된 props), memoizedState (직전 state — 함수형은 hook chain), updateQueue, dependencies (context 구독) | render 중·후 데이터 |
| 이펙트/우선순위 | flags, subtreeFlags, deletions, lanes, childLanes, mode | commit해야 할 일과 그 우선순위 |
| double buffer | alternate | 짝꿍 fiber 포인터 |
tag 종류 (ReactWorkTags.js)
가장 흔한 것:
0HostRoot — root container1FunctionComponent — 함수형 컴포넌트1ClassComponent5HostComponent — DOM 노드 (div, span 등)6HostText — text 노드7Fragment9ContextProvider /10ContextConsumer13SuspenseComponent15MemoComponent /16SimpleMemoComponent- ... 등 26종
각 tag마다 beginWork/completeWork/commit*의 switch 분기가 따로 있다.
3. 단방향 Linked List Tree
이게 Fiber의 첫 번째 묘미다. 트리지만 메모리상에선 child 1개 + sibling 포인터만 있다.
App
│ child
▼
Header ──sibling──► Main ──sibling──► Footer
│ child │ child │ child
▼ ▼ ▼
... ... ...
각 자식 fiber에는 return이 부모를 가리킨다 (이름이 parent가 아니라 return인 이유는 "JS call stack에서 이 함수가 끝나면 어디로 return할지"의 비유다).
왜 이렇게 만들었나
- 다음에 처리할 fiber가 명확 — child가 있으면 거기로, 없으면 sibling, sibling도 없으면 return의 sibling
completeUnitOfWork이 정확히 이 알고리즘:
react-main/packages/react-reconciler/src/ReactFiberWorkLoop.js:3347-3404
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork: Fiber = unitOfWork;
do {
...
const current = completedWork.alternate;
const returnFiber = completedWork.return;
...
next = completeWork(current, completedWork, entangledRenderLanes);
...
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// Otherwise, return to the parent
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
...
}
정확히 "현재 fiber complete → sibling이 있으면 거기로 → 없으면 return으로 올라가서 다시 그 sibling 보기". DFS post-order의 정통 구현.
- 언제든 work loop을 멈춰도
workInProgress포인터 하나로 어디까지 했는지 복구 가능 — 이게 concurrent 렌더링의 전제
4. Double Buffer — current ↔ work-in-progress
게임 그래픽의 double buffering에서 차용한 아이디어. 항상 두 트리가 메모리에 있고, 짝꿍 fiber끼리 alternate 포인터로 연결:
current tree (화면에 보이는 것)
App ◄──alternate──► App (work-in-progress)
... ...
새 render가 시작되면 React는 현재 트리를 건드리지 않고, alternate(없으면 새로 만들고 짝지음)를 work-in-progress 트리로 사용해 거기에 변경사항을 쌓는다. Commit이 끝나면 root.current = workInProgress로 포인터 한 줄 바꿔치기 — 이것이 트리 전환의 전부.
createWorkInProgress 실제 코드
react-main/packages/react-reconciler/src/ReactFiber.js:327-414
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
...
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// Needed because Blocks store data on type.
workInProgress.type = current.type;
// We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags;
// The effects are no longer valid.
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
...
}
...
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
...
}
핵심 관찰
- 두 fiber만 메모리에 둔다 — 트리의 모든 노드에 대해 (메모리 효율). 주석: "we'll only ever need at most two versions of a tree"
- 재활용 풀(pooling) —
alternate가 이미 있으면 새로 만들지 않고 필드만 reset stateNode는 공유 — 실제 DOM 노드는 둘 다 같은 것을 가리킴. Fiber만 두 벌, host instance는 한 벌.flags,subtreeFlags,deletions는 reset — 이번 렌더 동안 다시 채워질 것child/memoizedProps/memoizedState/updateQueue는 일단 current에서 복제 — 그 후 reconciliation이 필요하면 수정
이게 React가 "렌더 중에도 화면은 멀쩡"한 비결이다. work-in-progress가 어떻게 되든 current는 계속 사용자가 보는 진실의 원천.
5. Two-Phase Work: Render + Commit
| Phase | 책임 | 중단 가능? | 트리 |
|---|---|---|---|
| Render | 새 work-in-progress 트리를 만들고 effects를 표시 | ✓ (Concurrent에서) | work-in-progress 작성 |
| Commit | 트리를 화면에 반영, effects를 실제로 실행 | ✗ (atomic) | current ↔ work-in-progress 스왑 |
Render는 다시 두 phase로 나뉜다:
- Begin phase — 트리를 내려가며
beginWork호출 - Complete phase — 자식이 끝나면 올라오며
completeWork호출
performUnitOfWork이 한 fiber 단위를 처리하는데, 그 안에서 beginWork 호출 → 자식이 있으면 workInProgress = next로 내려가고, 자식이 없으면 completeUnitOfWork로 올라오는 phase로 전환:
react-main/packages/react-reconciler/src/ReactFiberWorkLoop.js:3060-3102
function performUnitOfWork(unitOfWork: Fiber): void {
...
next = beginWork(current, unitOfWork, entangledRenderLanes);
...
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
이 한 함수가 fiber tree DFS의 정확한 구현이다. work loop은 이 함수를 반복 호출할 뿐.
6. Begin Phase (beginWork) 상세
react-main/packages/react-reconciler/src/ReactFiberBeginWork.js:4168-4222
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
...
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
...
) {
// If props or context changed, mark the fiber as having performed work.
didReceiveUpdate = true;
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
...
}
}
...
}
Begin phase가 하는 일 3가지
-
Bailout 결정: props가 그대로이고 이 fiber/childLanes에 예정된 업데이트가 없으면 → 이 subtree 전체 skip. 이게 React의 "변경되지 않은 부분은 그냥 건너뛴다" 매커니즘.
oldProps !== newProps비교가 reference equality라는 게 중요 — 부모가 매번 새 객체 prop을 만들면 bailout 무효화. -
Tag별 처리:
switch (workInProgress.tag)로 갈라져서:FunctionComponent→ 함수 본체 실행 → JSX 반환받음 → 자식 element들을 받음ClassComponent→ 인스턴스의render()호출HostComponent(DOM 노드) → children prop 그대로 사용ContextProvider→ context value 변경되었는지 비교 → 변경시 구독자 fiber에 lane 표시
-
Reconciliation: 받은 자식 elements와 기존
current.childlinked list를 비교해 work-in-progress의 child linked list를 만듦. 이 과정에서 각 child fiber에Placement,Update,Deletion같은 flag가 박힘.
beginWork은 workInProgress.child를 반환하고 (혹은 null), work loop이 그걸로 내려간다.
7. Complete Phase (completeWork) 상세
react-main/packages/react-reconciler/src/ReactFiberCompleteWork.js:1068-1117
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
...
popTreeContext(workInProgress);
switch (workInProgress.tag) {
...
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
...
bubbleProperties(workInProgress);
return null;
...
case HostRoot: {
const fiberRoot = (workInProgress.stateNode: FiberRoot);
...
}
...
}
}
Complete phase가 하는 일
- Host effects 생성:
HostComponent(DOM 노드)면 실제 DOM 인스턴스를 만들거나 (mount), 변경된 props를updatePayload로 계산해flags |= Update(update). 이때 host instance가 만들어지므로 render phase 마지막에 DOM이 메모리에 준비됨 (commit phase에서 attach만 하면 됨). bubbleProperties호출 — 이 부분이 핵심:- 자식들의
subtreeFlags를 OR로 합산 - 자식들의
lanes/childLanes를 OR로 합산해 자기childLanes에 기록 - 결과: root에 도달하면 root의
subtreeFlags가 "트리 어딘가에 어떤 effect가 있나"를 한 비트마스크로 압축해서 들고 있음. Commit phase가 빠르게 traverse할 수 있는 이유.
- 자식들의
bubbleProperties가 정확히 fiber 아키텍처의 bottom-up 정보 집계 메커니즘이다.
8. Effects는 Bitmask로 산다
react-main/packages/react-reconciler/src/ReactFiberFlags.js:18-39
export const NoFlags = /* */ 0b0000000000000000000000000000000;
export const PerformedWork = /* */ 0b0000000000000000000000000000001;
export const Placement = /* */ 0b0000000000000000000000000000010;
...
export const Update = /* */ 0b0000000000000000000000000000100;
...
export const ChildDeletion = /* */ 0b0000000000000000000000000010000;
export const ContentReset = /* */ 0b0000000000000000000000000100000;
export const Callback = /* */ 0b0000000000000000000000001000000;
...
export const Ref = /* */ 0b0000000000000000000001000000000;
export const Snapshot = /* */ 0b0000000000000000000010000000000;
export const Passive = /* */ 0b0000000000000000000100000000000;
...
export const Visibility = /* */ 0b0000000000000000010000000000000;
각 fiber에 두 비트마스크:
flags: 이 fiber 자체에 어떤 effect가 필요한가subtreeFlags: 이 fiber의 subtree에 (자기 자신 제외) 어떤 effect가 있나 —completeWork의bubbleProperties가 채움
세 가지 commit phase에 대응하는 마스크:
react-main/packages/react-reconciler/src/ReactFiberFlags.js:116-132
export const MutationMask =
...
export const LayoutMask = Update | Callback | Ref | Visibility;
...
export const PassiveMask = Passive | Visibility | ChildDeletion;
| Mask | 의미 | Commit phase |
|---|---|---|
MutationMask | DOM 변경 (Placement, Update content, deletion 등) | 1. Mutation phase |
LayoutMask | useLayoutEffect, ref, callback 등 — DOM mutation 직후 동기로 | 2. Layout phase |
PassiveMask | useEffect — DOM mutation 후 비동기로 | 3. Passive phase |
이 비트마스크 덕에 commit phase가 fiber 전체를 traverse하면서 "이 subtree에 mutation이 있나?"를 1번의 비트 AND로 결정. 없으면 subtree 통째로 skip.
9. Commit Phase 3단계
export function commitMutationEffects(
export function commitLayoutEffects(
function flushPassiveEffects(): boolean {
순서
-
commitMutationEffects— DOM 변경 적용. 이 phase 안에서:Snapshotflag가 있는 클래스 컴포넌트의getSnapshotBeforeUpdate호출ChildDeletion이 있으면 해당 fiber 트리를 unmount →componentWillUnmount/useLayoutEffect cleanup호출 → DOM에서 제거Placement: 새 노드 attachUpdate: prop 변경 적용- 이 phase 종료 시점이 정확히 "
current↔workInProgress스왑이 일어나는 순간"
-
commitLayoutEffects— DOM이 update된 직후, 브라우저가 paint하기 전:useLayoutEffectbody 실행componentDidMount/componentDidUpdate- ref attach/detach (
useRef는 mutation phase에서 처리, callback ref는 layout) - 동기. 여기서 setState하면 추가 render가 같은 commit batch 안에서 일어남.
-
flushPassiveEffects— 다음 microtask 또는 idle 시점:useEffectcleanup → body 순서로 실행useInsertionEffect도 여기 (정확히는 더 일찍이지만 같은 phase 계열)
왜 이 순서인가
useLayoutEffect가 paint 전에 실행되는 이유 = layout측정 후 추가 setState로 paint를 보정할 수 있어야 하기 때문. useEffect가 늦게 실행되는 이유 = layout/paint를 blocking하지 않기 위함. 18에서 추가된 useInsertionEffect는 layout effect보다도 더 일찍 — <style> 주입이 layout 측정을 깨지 않게.
10. Hooks는 Fiber에 Linked List로 저장
react-main/packages/react-reconciler/src/ReactFiberHooks.js:194-200
export type Hook = {
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: any,
next: Hook | null,
};
함수형 컴포넌트의 fiber.memoizedState는 첫 번째 hook을 가리킨다. 그 hook의 next가 다음 hook, ... 이렇게 singly linked list.
fiber.memoizedState
│
▼
useState ─next─► useEffect ─next─► useState ─next─► null
│ │ │
{state, queue} {effect, deps} {state, queue}
함의
- Hook 순서는 절대 바뀌면 안 된다 — index가 아니라 호출 순서에 의존. 첫 번째 hook은 list의 첫 노드, ... 만약 조건문 안에서 hook을 호출해 순서가 달라지면 어떤 fiber에선
useState자리에useEffect의 state가 매핑되어 버린다. React가 lint로 막는 이유. - Hook은 fiber 안에 산다 — 컴포넌트 인스턴스가 곧 fiber. 그래서 같은 컴포넌트도 두 번 마운트되면 별개의 hook chain을 가진다.
- Effect도 hook:
Hook.memoizedState에Effect객체가 들어 있고, 효과 chain은 별도로fiber.updateQueue에도 연결. Commit phase가 그걸 순서대로 실행.
react-main/packages/react-reconciler/src/ReactFiberHooks.js:220-226
export type Effect = {
tag: HookFlags,
inst: EffectInstance,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
next: Effect,
};
Effect.tag 비트로 Layout/Passive/Insertion 구분. 같은 commit phase 코드가 비트마스크 하나로 어떤 effect 종류를 실행할지 결정.
11. Lanes와 Fiber
react-main/packages/react-reconciler/src/ReactFiberLane.js:47-107
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
...
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
...
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
...
export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000;
각 fiber는 두 lane 마스크:
lanes: 이 fiber 자체에 pending 업데이트들의 lane 합childLanes: subtree 안 어딘가에 pending 업데이트의 lane 합 —completeWork의bubbleProperties가 채움
Work loop이 lane을 골랐을 때, 그 lane이 fiber.lanes에도 childLanes에도 없으면 그 subtree 전체 skip — attemptEarlyBailoutIfNoScheduledUpdate. 이게 React가 "내가 지금 처리하는 priority와 무관한 fiber는 안 봐도 됨"을 결정하는 메커니즘.
Concurrent 렌더링이 fiber 위에서 동작하는 그림
1. 이벤트 발생 → setState 호출
2. fiber.lanes에 lane 추가 + 조상들의 childLanes에 OR
3. ensureRootIsScheduled → microtask
4. processRootScheduleInMicrotask → getNextLanes로 가장 높은 lane 선택
5. renderRootConcurrent:
- root에서 시작해 workInProgress = createWorkInProgress(root.current, ...)
- work loop이 fiber 단위로 beginWork → 자식 → completeWork
- workLoopConcurrent는 매 25ms/shouldYield 마다 양보 가능
6. yield 동안 더 급한 update가 들어오면:
- workInProgressRoot, workInProgressRootRenderLanes 비교
- stale → prepareFreshStack으로 새로 시작 (abandon)
7. complete → commit (mutation → layout → passive)
8. root.current = workInProgress
위 모든 단계가 Fiber의 트리 구조와 alternate 포인터, lane 비트마스크 위에서만 가능. Fiber 없이는 어느 한 줄도 동작 안 함.
12. Suspense / Error Boundary가 Fiber 단위로 작동하는 모습
Suspense
컴포넌트가 promise를 throw하면, React는 work loop에서 catch한다. 그 다음:
- 가장 가까운 Suspense boundary fiber를
return체인 따라 위로 찾아 올라감 - 그 fiber에
DidCaptureflag 설정 - 이번 렌더에선 그 boundary의 fallback을 child로 reconcile
- promise resolve 시 retry — 그 boundary부터 다시 render (트리 전체 재시작 X)
이게 가능한 이유는 fiber tree에 부모 포인터(return)가 있어서 boundary를 빠르게 찾을 수 있고, boundary가 자기 fallback과 primary children을 모두 알고 있기 때문.
Error Boundary
같은 메커니즘. componentDidCatch / getDerivedStateFromError가 정의된 fiber를 return 체인으로 찾아 그 boundary에 에러를 위임. 자식 트리는 unmount.
둘 다 fiber tree의 위상적(topological) 구조 위에서 동작 — 트리 walking + flag set이 핵심 메커니즘.
13. 큰 그림 — 한 setState가 commit까지 가는 모습
[1] setState 호출
│
│ scheduleUpdateOnFiber(fiber, lane)
▼
[2] fiber.lanes |= lane
return 체인으로 올라가며 조상의 childLanes |= lane
│
▼
[3] ensureRootIsScheduled(root)
│
│ scheduleMicrotask(processRootScheduleInMicrotask)
▼
[4] microtask: getNextLanes로 우선순위 결정
│
▼
[5] renderRootConcurrent(root, lanes):
│
│ workInProgress = createWorkInProgress(root.current)
▼
[6] WORK LOOP (Render Phase):
│
│ while (workInProgress !== null && !shouldYield())
│ performUnitOfWork(workInProgress):
│ beginWork(current, workInProgress): [내려가는 phase]
│ - props 비교, bailout 결정
│ - tag별 처리 (FC면 함수 실행, HC면 children 채택)
│ - reconcileChildren → 자식 fiber linked list 생성
│ if (next === null):
│ completeUnitOfWork(workInProgress): [올라오는 phase]
│ completeWork:
│ - HostComponent면 DOM instance 생성/diff
│ - bubbleProperties로 subtreeFlags·childLanes 합산
│ sibling 있으면 거기로, 없으면 return으로 올라감
▼
[7] root에 도달 → workInProgress 트리 완성
│
▼
[8] COMMIT PHASE (atomic):
│
│ commitMutationEffects: DOM 변경 적용
│ ├ deletion: lifecycle cleanup + DOM remove
│ ├ placement: DOM attach
│ └ update: prop diff 적용
│
│ *** root.current = workInProgress *** ← 트리 스왑 한 줄
│
│ commitLayoutEffects: paint 전, 동기
│ ├ useLayoutEffect body
│ └ componentDidMount/Update, ref attach
│
│ flushPassiveEffects: microtask/idle, 비동기
│ ├ useEffect cleanup
│ └ useEffect body
▼
[9] 끝. 다음 update를 기다림.
매 단계가 fiber 노드 구조의 어떤 필드를 읽고 쓰는지가 명확하다. Fiber는 단순한 자료구조가 아니라 React의 실행 의미론(operational semantics) 그 자체다.
14. 결론 — Fiber는 모든 신기능의 전제
| 기능 | Fiber의 어떤 측면에 의존 |
|---|---|
| Concurrent Rendering | 단방향 linked list tree + workInProgress 포인터 1개로 중단점 표현 |
| Lane 우선순위 | fiber.lanes/childLanes bitmask + work loop의 lane 선택 |
| Double Buffering | alternate 포인터, "렌더 중에도 화면은 멀쩡" |
| Bailout 최적화 | props reference 비교 + childLanes skip |
| Suspense | return 체인 walking + boundary fiber에 DidCapture flag |
| Error Boundary | 같은 메커니즘 — 트리 위상 walking |
| Selective Hydration | Suspense boundary 단위 fiber 트리 부분 hydration |
| Hooks | fiber.memoizedState의 singly linked Hook chain |
| Commit 3-phase | flags/subtreeFlags bitmask + MutationMask/LayoutMask/PassiveMask |
| RSC | 서버에서 만든 element 트리가 클라이언트에서 fiber 트리로 reconcile |
한 줄 요약: Fiber는 "reconciliation 상태를 JS call stack에서 빼내어 명시적·중단가능·우선순위가 있는 자료구조로 옮긴 것" 이며, 이 결정 이후의 React 진화(16~19)는 거의 전부 Fiber의 어떤 측면을 더 활용하는 방향이었다.