리액트의 초기 마운트
React가 초기 마운트(최초 렌더링)의 수행과정
1. Fiber Architecture에 대한 간략한 소개
Fiber는 React가 App 상태의 내부 표현을 유지하는 아키텍처입니다. FiberRootNode와 FiberNode들로 구성된 트리와 같은 구조입니다. 여러 종류의 FiberNode가 있으며 그 중 일부는 DOM 노드를 가지고 있는 HostComponent입니다.
React 런타임은 Fiber 트리를 유지하고 업데이트하며 최소한의 업데이트로 DOM을 동기화 하려고 하고있습니다
여기서 HostComponets란??
HostComponent는 React에서 실제 DOM 요소와 직접적으로 매핑되는 컴포넌트를 의미합니다. 쉽게 말해서
- HTML 요소들이 HostComponent입니다. 예를 들면
<div>
<span>
<p>
<input>
<button>
등이 모두 HostComponent입니다.
- 반면에 우리가 만드는 React 컴포넌트들은 HostComponent가 아닙니다
// 이건 HostComponent가 아님 (FunctionComponent)
function MyComponent() {
return <div>Hello</div>;
}
// 이것도 HostComponent가 아님 (ClassComponent)
class AnotherComponent extends React.Component {
render() {
return <span>World</span>;
}
}
HostComponent가 중요한 이유는:
- 실제 DOM과 직접 연결되어 있어서 브라우저에 렌더링되는 실제 요소를 나타냅니다
stateNode
속성이 실제 DOM 노드를 참조합니다- React가 이 컴포넌트들을 통해 실제 DOM 업데이트를 수행합니다
즉, HostComponent는 React의 가상 세계와 브라우저의 실제 DOM을 연결해주는 다리 역할을 한다고 볼 수 있습니다.
1.1 FiberRootNode
FiberRootNode는 React 루트 역할을 하는 특별한 노드로, 전체 앱에 대한 필요한 정보를 보유합니다.
FiberRootNode의 current
는 실제 Fiber 트리를 가리키며, 새로운 Fiber 트리가 구성될 때마다 current
를 새로운 HostRoot
로 다시 가리킵니다.
1.2 FiberNode
FiberNode는 FiberRootNode를 제외한 모든 노드를 의미하며, 중요한 속성은 다음과 같습니다
1. `tag`: FiberNode는 `tag`로 구분되는 여러 하위 타입을 가집니다. 예를 들어, FunctionComponent, HostRoot, ContextConsumer, MemoComponent, SuspenseComponent 등이 있습니다.
2. `stateNode`: 다른 백업 데이터를 가리킵니다. `HostComponent`의 경우 `stateNode`는 실제 백업 DOM 노드를 가리킵니다.
3. `child`, `sibling`, `return`: 이들은 함께 트리와 같은 구조를 형성합니다.
4. `elementType`: 우리가 제공하는 컴포넌트 함수나 내장 HTML 태그입니다.
5. `flags`: Commit 단계에서 적용할 업데이트를 나타냅니다. `subtreeFlags`는 하위 트리에 대한 것입니다.
6. `lanes`: 대기 중인 업데이트의 우선순위를 나타냅니다. `childLanes`는 하위 트리에 대한 것입니다.
7. `memoizedState`: 중요한 데이터를 가리킵니다. FunctionComponent의 경우 이는 훅들을 의미합니다.
2. 트리거 단계에서의 초기마운트
React의 초기 마운트 과정:
- 진입점과 특징
createRoot()
로 React 앱 초기화- FiberRootNode 생성
- 더미 HostRoot FiberNode를 current로 생성
root.render()
가 호출되면- HostRoot에 업데이트 예약
- 전달된 엘리먼트를 업데이트 payload에 저장
performConcurrentWorkOnRoot()
가 시작점- 초기 마운트는 동시성 모드를 사용하지 않고 동기적으로 처리
- DefaultLane이 blocking lane이기 때문
- Fiber 트리 작업 과정
current
: 현재 화면에 표시된 Fiber 트리workInProgress
: 새로 만들어지는 Fiber 트리prepareFreshStack()
으로 새로운 workInProgress 트리 준비renderRootSync()
에서 while 루프로 작업 수행workLoopSync()
- workInProgress가 null이 될 때까지 반복
- 각 반복마다
performUnitOfWork
호출
- 컴포넌트 타입별 처리
- HostRoot
- FiberRootNode의 자식
updateHostRoot
로 처리
- IndeterminateComponent
mountIndeterminateComponent
로 처리- 렌더링 후 정확한 컴포넌트 타입으로 태그 변경
- FunctionComponent
updateFunctionComponent
로 처리- renderWithHooks 호출
- HostComponent
updateHostComponent
로 처리- props와 children 관리
- HostText
updateHostText
로 처리
- 텍스트 처리의 두 가지 방식
- 정적 텍스트 (예:
<a>텍스트</a>
)shouldSetTextContent
로 최적화 여부 결정finalizeInitialChildren
단계에서 처리- 부모 DOM 노드의 textContent로 직접 설정
- 별도의 DOM 노드 생성 없음
- 동적 텍스트 (예:
<button>{count}</button>
)- reconciliation 과정에서 별도 처리
createTextInstance
로 새 텍스트 노드 생성- 표현식이 포함된 경우 이 방식 사용
- DOM 생성 과정
completeWork()
단계에서 실제 DOM 노드 생성createInstance
로 실제 DOM 요소 생성appendAllChildren
으로 DOM 트리 구성- stateNode 속성에 실제 DOM 노드 참조 저장
- 부모-자식 관계 설정
finalizeInitialChildren
으로 초기 속성 설정과 이벤트 리스너 등록
- 최적화 포인트
- 초기 마운트는 가능한 빨리 UI를 그리는 것이 목표
shouldTrackSideEffects
플래그로 불필요한 작업 방지- 불필요한 DOM 노드 생성을 피하기 위한 최적화 (정적 텍스트 처리 등)
- 트리 구조를 연결 리스트로 평탄화하여 효율적인 순회 가능
- Reconciliation (조정) 과정
reconcileChildren()
으로 자식 요소들 처리reconcileSingleElement
: 단일 엘리먼트 처리reconcileChildrenArray
: 배열 처리placeSingleChild
: DOM 삽입 필요성 표시- 초기 마운트:
mountChildFibers()
사용 - 업데이트:
reconcileChildFibers()
사용 - key를 사용한 효율적인 리스트 비교
- 부수 효과 처리
- Placement: DOM 삽입 필요
- ContentReset: 텍스트 콘텐츠 리셋 필요
- Ref: 참조 설정 필요
- Update: props 업데이트 필요
- DEV 플래그에 따른 추가 검사와 디버깅 정보 설정
Commit Phase 처리단계
React의 Commit 단계 초기 마운트 과정은 다음과 같습니다:
- Commit 단계 시작 시점
- workInProgress 버전의 Fiber 트리가 완성되었습니다.
- 실제 DOM 노드들이 생성되고 구성되었습니다.
- DOM 조작을 위한 flags가 필요한 fiber들에 설정되었습니다.
- commitMutationEffects 실행
- DOM 변경을 처리하는 핵심 함수입니다.
- 구조적으로 다음과 같이 이루어집니다:
commitMutationEffects
: 전체 프로세스를 시작합니다.commitMutationEffectsOnFiber
: 각 Fiber 노드별로 처리합니다.recursivelyTraverseMutationEffects
: 하위 트리를 먼저 처리합니다.commitReconciliationEffects
: 실제 DOM 조작을 수행합니다.
- deletion이 있는 경우, 자식 효과보다 먼저 처리됩니다.
parentFiber.subtreeFlags & MutationMask
로 하위 트리의 mutation 효과를 확인합니다.- 에러 발생 시
captureCommitPhaseError
로 처리됩니다.
- Fiber 타입별 처리
- FunctionComponent
- HostComponent
- HostText
- HostRoot
- HostSingleton (활성화된 경우)
각각 동일한 패턴으로 처리됩니다:- 하위 트리 효과를 처리합니다.
- 조정 효과를 처리합니다.
- commitReconciliationEffects 처리
- Placement(삽입), 재정렬 등을 처리합니다.
- flags를 확인한 후 필요한 DOM 조작을 수행합니다.
- Placement 플래그가 있는 경우
commitPlacement
를 호출합니다. - 처리 후 Placement 플래그를 제거하여 componentDidMount 전에 삽입이 완료됨을 보장합니다.
- commitPlacement 실행
- 부모 Fiber 타입에 따라 다르게 처리됩니다:
- HostComponent: ContentReset 플래그가 있으면 텍스트 콘텐츠를 리셋합니다.
- HostRoot: stateNode가 FiberRootNode를 가리킵니다.
- HostPortal: 특별한 컨테이너 처리를 합니다.
- HostSingleton: 단일 노드로 특별 처리됩니다.
- 실제 DOM 삽입
insertOrAppendPlacementNodeIntoContainer
를 통해:- DOM 요소(HostComponent, HostText)는 직접 삽입/추가됩니다.
- 비 DOM 요소는 자식들을 재귀적으로 처리합니다.
- HostPortal의 경우 자식들이 개별적으로 삽입을 처리합니다.
- 삽입 위치는
getHostSibling
을 통해 결정됩니다:- before가 있으면: insertInContainerBefore를 사용합니다.
- before가 없으면: appendChildToContainer를 사용합니다.
- 초기 마운트의 특징
<App/>
컴포넌트가 Placement 플래그를 가집니다.- HostRoot가 부모 Fiber로 작동합니다.
- 재귀적으로 모든 자식 DOM 노드들이 처리됩니다.
이러한 과정을 통해 React는 준비된 DOM 노드들을 실제 웹 페이지에 반영하게 됩니다.
초기마운트의 최종과정
React의 초기 마운트 전체 과정을 다음과 같이 요약할 수 있습니다:
- 초기 설정 단계
createRoot()
로 시작- FiberRootNode 생성
- 더미 HostRoot FiberNode를 current로 생성
root.render()
- HostRoot에 업데이트 예약
- 전달된 엘리먼트를 업데이트 payload에 저장
- Render 단계
performConcurrentWorkOnRoot()
에서 시작- 초기 마운트는 동기적으로 처리 (DefaultLane이 blocking)
- Fiber 트리 구성:
- current: 현재 화면의 Fiber 트리
- workInProgress: 새로 만들어지는 Fiber 트리
- 컴포넌트 처리:
- HostRoot → IndeterminateComponent → FunctionComponent/HostComponent → HostText
- 각 타입별로 적절한 처리 함수 호출
- 텍스트 처리:
- 정적 텍스트: 부모 노드의 textContent로 직접 설정
- 동적 텍스트: 별도의 텍스트 노드로 생성
- Reconciliation (조정) 단계
reconcileChildren()
으로 자식 요소 처리- 초기 마운트:
mountChildFibers()
사용 - DOM 노드 생성:
completeWork()
에서 실제 DOM 노드 생성- stateNode에 DOM 노드 참조 저장
- 부모-자식 관계 설정
- Commit 단계
commitMutationEffects
실행- 하위 트리부터 처리
- deletion은 자식 효과보다 먼저 처리
- DOM 조작:
- Placement 플래그 확인
commitPlacement
로 DOM 노드 삽입- 부모 타입에 따라 다른 처리 방식 적용
- 실제 DOM 반영:
- DOM 요소는 직접 삽입
- 비 DOM 요소는 자식들을 재귀적으로 처리
- 위치에 따라 적절한 삽입 메서드 사용