리액트 Fiber 아키텍처: 렌더링 주도권을 되찾아온 리액트의 엔진 교체기

리액트는 왜 렌더링 알고리즘을 완전히 새로 썼을까요? 애니메이션과 인터랙션을 방해하지 않는 비차단(Non-blocking) 렌더링의 핵심, Fiber 아키텍처를 파헤칩니다.

ReactVirtualDOMFiber
--

리액트 v16 이전의 재조정 알고리즘(Stack Reconciler)은 한 번 작업을 시작하면 멈출 수 없는 '동기적(Synchronous)' 방식이었습니다.

트리가 거대해지면 자바스크립트 엔진이 메인 스레드를 오래 점유하게 되고, 그 사이 발생한 사용자의 입력이나 애니메이션은 프레임 드랍(Jank)으로 이어졌죠.

리액트 팀은 이 문제를 해결하기 위해 엔진을 통째로 갈아 끼웠습니다. 그것이 바로 리액트 Fiber입니다.

1. Stack Reconciler의 한계: "멈출 수 없는 폭주 기관차"

기존 알고리즘은 자바스크립트의 **호출 스택(Call Stack)**에 의존했습니다. 재귀적으로 트리를 탐색하며 실제 DOM에 변경 사항을 적용할 때까지 멈추지 않았죠.

  • 문제점: 렌더링 작업이 16ms(60fps 기준) 이상 걸리면 화면이 끊깁니다.
  • 비유: 한 입에 다 먹어야 하는 거대한 햄버거와 같습니다. 다 먹을 때까지 숨도 못 쉬고 다른 일도 못 하는 상태죠.

2. Fiber란 무엇인가?: "작업의 최소 단위"

Fiber는 리액트에서 관리하는 **작업 단위(Unit of Work)**이자, 자바스크립트 객체입니다.

Fiber의 핵심 목표는 렌더링 작업을 잘게 쪼개고, 우선순위를 부여하며, 필요하다면 작업을 일시 중단하거나 폐기하는 것입니다.

Fiber 노드의 구조

Fiber는 단순한 가상 DOM 노드를 넘어, 자체적인 상태와 작업 스케줄링 정보를 가집니다.

{
type: 'div',
key: null,
stateNode: ..., // 실제 DOM 노드 참조

// 트리 구조를 위한 링크 (Linked List 방식)
return: Fiber, // 부모 노드
child: Fiber, // 첫 번째 자식 노드
sibling: Fiber, // 형제 노드

// 작업 우선순위 및 상태
lanes: Lane, // 작업의 우선순위 (Lane 모델)
alternate: Fiber, // 반대편 트리(Current ↔ WorkInProgress) 참조
flags: Flags, // DOM에 적용해야 할 변경 사항 (배치, 업데이트 등)
}

기존의 재귀적 트리 구조와 달리, 연결 리스트(Linked List) 형태의 데이터 구조를 가짐으로써

리액트는 언제든 트리를 타다가 멈추고, 나중에 다시 돌아올 지점을 기억할 수 있게 되었습니다.


3. Fiber의 2단계 생명주기: Render & Commit

Fiber 아키텍처는 작업을 두 단계로 명확히 분리합니다.

1단계: Render Phase (Asynchronous)

  • 하는 일: 새로운 가상 DOM 트리를 빌드하고 이전 트리와 비교하여 변경 사항을 찾아냅니다 (Diffing).
  • 특징: 비동기적입니다. 우선순위가 높은 작업(예: 타이핑)이 들어오면 작업을 중단하고 메인 스레드를 양보합니다.
  • 결과: DOM에 바로 반영하지 않고, 변경 사항이 기록된 **'가공된 트리(WorkInProgress Tree)'**를 만듭니다.

2단계: Commit Phase (Synchronous)

  • 하는 일: Render Phase에서 찾아낸 변경 사항들을 실제 DOM에 한꺼번에 적용합니다.
  • 특징: 동기적입니다. 일관된 UI를 위해 이 단계는 중단될 수 없습니다. componentDidMountuseLayoutEffect가 실행되는 시점입니다.

4. Lane 모델: 우선순위 스케줄링

Fiber는 어떤 작업이 더 중요한지 어떻게 판단할까요? 리액트는 **Lane(레인)**이라는 개념을 사용합니다. 각 작업에 비트를 할당하여 우선순위를 관리하죠.

  • Sync Lane: 사용자 입력(Input) 등 즉각적인 반응이 필요한 작업 (최고 우선순위)
  • Input Continuous Lane: 스크롤, 드래그 등 연속적인 상호작용
  • Default Lane: 네트워크 요청 등으로 인한 데이터 업데이트
  • Idle Lane: 굳이 지금 안 해도 되는 백그라운드 작업

이를 통해 리액트는 화면을 그리는 도중 사용자가 글자를 입력하면, 그리던 것을 멈추고 입력된 글자를 먼저 화면에 보여준 뒤 다시 남은 렌더링을 이어갑니다.


5. Double Buffering: 안정적인 전환

리액트 Fiber는 두 개의 트리를 관리합니다.

  1. Current Tree: 현재 화면에 보여지고 있는 실제 DOM과 매칭되는 트리.
  2. WorkInProgress Tree: 현재 작업 중인 새로운 트리.

Commit Phase가 끝나면 리액트는 단순히 포인터를 교체(Swap)하여 WorkInProgress 트리를 Current 트리로 바꿉니다. 이는 그래픽 프로그래밍의 더블 버퍼링 기법과 유사하며, 사용자에게 중간 과정이 노출되지 않는 매끄러운 화면 전환을 보장합니다.


🎯 개발자가 얻는 이득은?

우리가 직접 Fiber 객체를 조작할 일은 거의 없습니다. 하지만 이 아키텍처 덕분에 다음과 같은 강력한 기능들이 가능해졌습니다.

  1. Concurrent Mode: useTransition, useDeferredValue를 통해 무거운 렌더링 중에도 UI 응답성을 유지할 수 있습니다.
  2. Suspense: 데이터 로딩 상태를 선언적으로 관리하고, 준비가 되었을 때만 화면을 교체할 수 있습니다.
  3. 에러 경계(Error Boundaries): 렌더링 도중 에러가 발생해도 전체 앱이 죽지 않고 부분적으로 복구할 수 있습니다.

마치며

React Fiber는 단순히 성능을 개선한 것이 아니라, UI 업데이트의 주도권을 브라우저에서 리액트 스케줄러로 가져온 혁명적인 사건입니다.

"리액트는 왜 빠른가?"라는 질문에 이제는 "가상 DOM 덕분이다"를 넘어, **"작업을 잘게 쪼개서 우선순위에 따라 스케줄링하는 Fiber 엔진 덕분이다"**라고 답할 수 있을 것입니다.

댓글

0/2000
Newsletter

이 글이 도움이 되셨나요?

새로운 글이 발행되면 이메일로 알려드립니다.

뉴스레터 구독하기