리액트의 코어 아키텍처 Fiber와 Fiber 재조정자에 의한 재조정 과정
현재 리액트는 16버전에 도입된 Fiber 아키텍처를 기반으로 가상돔을 실제 DOM과 동기화하는 재조정(Reconciliation)과정을 진행합니다. 그리고 이 Fiber 덕분에
startTransition
과 같은 업데이트 우선순위 기능을 구현 할 수 있는 것인데요. 이번 포스팅에서는 Fiber란 무엇인지, 어떻게 재조정 과정을 진행하는 것인지 살펴보겠습니다.
Fiber란?
Fiber는 React가 “어떻게 컴포넌트를 렌더링할지, 언제 DOM을 업데이트할지”를 결정하는 핵심 데이터 구조입니다. JSX으로 작성된 코드는 리액트에서 React.createElement
를 통해 리액트 엘리먼트 트리로 만듭니다. 이때 리액트는 추가정보를 포함하기 위해 리액트 엘리먼트 트리를 기반으로 Fiber 트리를 만들어 내는데요. 리액트 엘리먼트를 createFiberFromTypeAndProps
함수를 통해 파이버 노드를 반환합니다. Fiber 노드에는 다양한 정보가 포함되어 있지만, 간단히 리액트 엘리먼트와의 차이를 말하자면 Fiber는 상태를 저장하고 수명이 긴 반면, 리액트는 임시적이고 상태가 없다는 점이 있습니다. 그밖의 Fiber 노드에 포함된 정보는 다음과 같습니다.
필드명 | 설명 |
---|---|
tag |
이 Fiber가 어떤 타입의 컴포넌트인지 나타냅니다 (예: FunctionComponent, ClassComponent 등). 숫자 enum입니다. |
key |
리스트 렌더링 시 사용하는 고유 식별자 (props.key ). |
elementType |
JSX에서의 원래 타입 (예: div , MyComponent ). |
type |
elementType 과 비슷하지만, HOC를 거친 실제 타입일 수 있음. |
stateNode |
이 Fiber에 해당하는 실제 DOM 노드나 class component 인스턴스를 참조합니다. |
return |
부모 Fiber를 가리키는 참조입니다. |
child |
첫 번째 자식 Fiber 노드를 가리킵니다. |
sibling |
다음 형제 Fiber 노드를 가리킵니다. |
index |
이 Fiber의 형제들 중 몇 번째인지 (리스트 렌더링 순서). |
ref |
useRef , createRef 등을 통해 지정된 ref 정보입니다. |
pendingProps |
아직 처리되지 않은 새로운 props. |
memoizedProps |
마지막 렌더에서 사용된 props. |
memoizedState |
마지막 렌더에서 사용된 state. |
updateQueue |
state 변경과 관련된 업데이트 큐입니다. |
alternate |
현재 Fiber와 work-in-progress Fiber 간의 연결 (이중 버퍼링 구조). |
flags |
이 Fiber에서 수행해야 할 작업 (예: Placement, Update, Deletion 등). |
lanes |
이 작업이 속한 렌더링 우선순위 Lane 정보입니다. |
childLanes |
자식 중 가장 높은 우선순위 Lane. |
Fiber 아키텍처의 등장 배경
Fiber 아키텍처는 기존의 Stack 기반의 재조정자의 문제를 해결하기 위해 등장했습니다. 16버전 이전의 재조정자는 스택 기반의 알고리즘을 통해 재조정을 진행하였는데요. 때문에 상태변경에 대한 우선순위를 조정할 수가 없었을 뿐만 아니라, 렌더링 작업을 쪼개서 처리하는 타임 슬라이싱이 불가능했습니다. 때문에 이미지와 같은 상황에서 자동완성 리스트를 업데이트하느라 타이핑이나 스크롤이 지연되어 좋지 않은 UX가 발생할 수 있었습니다.
그리고 리액트는 이러한 문제를 해결하기 위해 2년 넘게 연구하여 React Fiber 라는 핵심 알고리즘을 만들어냅니다. React Fiber로 인해서 리액트는 점진적 렌더링(렌더링 작업 분산처리) 뿐만 아니라 다음과 같은 기능을 구현할 수 있게 됩니다.
- 작업 도중 일시 중지, 중단, 재사용 가능
- 업데이트 유형에 따라 우선순위 부여 가능(
startTransition
,useDeferredValue
) - 새로운 동시성(concurrency) 기능 제공
Fiber 아키텍처 기반의 재조정 과정
💡 재조정(Reconciliation)이란, 상태나 props가 변경되어 컴포넌트가 다시 렌더링될 때, 이전의 가상 DOM 트리와 새롭게 생성된 가상 DOM 트리를 비교하여 실제 DOM에 최소한의 변경만 적용하는 과정을 말합니다.
파이버 재조정 과정은 크게 렌더링 단계와 커밋 단계로 이뤄지는데요. 렌더링 단계에서는 사용자에게 보여줄 변경된 DOM을 준비하며, 커밋 단계는 준비된 DOM을 사용자에게 보여주는 것입니다. 이렇게 나눠진 단계를 바탕으로 커밋단계 이전에 언제든 렌더링 된 작업을 폐기할 수 있기 때문에 작업을 중단하거나 재사용할 수 있는 것입니다. 아래에서는 렌더링 단계와 커밋 단계를 더 자세히 설명하며 재조정 과정을 소개하겠습니다.
렌더링 단계
렌더링 단계는 현재 트리에서 상태 변경 이벤트가 발생하면 시작됩니다. 루트 파이버 노드에서부터 트리를 내려가면서 업데이트가 필요한 경우 dirty
로 표시하고, 트리의 끝에 도달하면 다시 반대로 순회하면서 DOM 트리와 분리된 새로운 DOM 트리를 생성합니다. 이때 위에서 아래로 이동하는 과정을 beginWork
, 아래에서 위로 이동하는 과정을 completeWork
으로 구분합니다.
beginWork
beginWork의 argument
에는 현재 트리의 파이버 노드의 참조를 뜻하는 current
, dirty
로 표시되어 반환되는 노드인 workInProgress
, 그리고 리액트의 업데이트가 처리되는 레인(lane)을 나타내는 renderLanes
가 존재하는데, renderLanes
는 우선순위를 지정할 뿐 아니라 어떤 업데이트를 먼저 처리할지, 미룰지를 결정합니다.
completeWork
completeWork는 업데이트된 상태를 나타내는 실제 DOM 트리를 새롭게 생성합니다만, 아직 실제 DOM을 업데이트 하지는 않습니다. 만약 우선순위가 더 높은 업데이트가 예약되면 만들었던 UI를 버리고 높은 우선순위를 처리하게 됩니다. 그리고 이 과정이 우선순위를 부여할 수 있는 파이버 재조정자 기능의 핵심입니다.
커밋 단계
렌더링 단계가 완료되면 리액트는 commitRoot
함수를 호출하여 FiberRootNode
의 포인터를 현재 트리에서 작업용 트리로 전환하고, 작업용 트리를 새로운 현재 트리로 만듭니다.
이러한 과정을 거치는 이유는 React Fiber가 렌더링 과정에서 기존 화면이 영향 받지 않도록 이중 버퍼링을 사용하기 때문입니다. 즉, React는 화면을 업데이트할 때 두 개의 트리를 번갈아 가며 사용하며, 하나는 현재 실제 화면에 적용된 트리, 그리고 하나는 렌더링 단계에서 새롭게 계산된 트리입니다.
이 시점부터는 향후 모든 업데이트는 새로운 현재 트리를 기반으로 이루어지며, 이러한 과정을 통해 Fiber 재조정자에 의한 리액트의 재조정이 이뤄지게 되는 것입니다.
참고