목차
리액트의 랜더링 과정
1. 트리거 단계
컴포넌트가 그려지기 위해서는 액션 즉 트리거가 필요하다.
트리거의 예로는 다음 두개가 대표적이다.
1. 사용자가 페이지에 처음 방문했을 때 초기 렌더
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);
2. 상태가 업데이트됐을 때 리렌더
한 번의 렌더 사이클에서 하나의 업데이트를 처리하는 것은 상대적으로 많은 연산과 비용이 들기 때문에 한번의 dom 업데이트를 통해 처리하고자 한다.
2. 랜더 단계
랜더 단계도 초기랜더와 리렌더 두가지로 나누어 지게 된다.
이때 랜더는 컴포넌트의 호출을 의미한다.
1. 함수가 연쇄적으로 호출될 때 내부의 JSX는 React.createElement() 함수로 JSX를 리액트 요소로 변환한다.
2. 재귀적으로 트리를 따라 호출을 반복하다가 최종적으로 더 이상 컴포넌트가 반환되지 않으면 비로소 가상 DOM 트리가 그려지게 된다.
3. 첫 렌더에는 가상 DOM과 실제 DOM이 동기화되고 추후 렌더링에는 렌더마다 새로운 가상 DOM을 만들어내게 된다.
-> 초기랜더
초기 렌더에서 렌더 단계는 render() 메소드의 루트 컴포넌트를 호출한다.
function App() {
return (
<main>
<h1>hello world</h1>
<Item />
<Item />
</main>
);
}
function Item() {
return <div>I am a Child</div>;
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<App />
);
위 코드에서 render() 메소드가 호출되면 리액트는 createElement()로 <main>, <h1>, <div> 태그명의 HTML 요소들을 생성한다.
-> 리랜더
리렌더에서 렌더 단계는 상태 업데이트가 발생한 컴포넌트를 호출한다.

리렌더에서 렌더 단계는 이전에 생성한 가상 DOM 트리와 새로 만든 가상 DOM 트리를 비교해 실제 DOM에 반영할 변경 사항들을 파악한다. 최소한의 변경 사항만 파악하기 위해 상태 업데이트가 발생한 컴포넌트를 호출하고 새로운 가상 DOM 트리를 만들어내게 된다.
리액트가 이전 렌더와 다음 렌더의 변화를 비교하는 과정을 재조정이라고 한다.
3. 커밋 단계
커밋 단계에서는 직전 렌더 단계에서 두 가상 DOM 트리 간 변화를 실제 DOM에 적용하는 단계입니다. 커밋 단계 또한 초기 렌더와 리렌더에 다르게 동작한다.
- 초기 렌더에서의 커밋 단계는 렌더 단계에서 파악한 DOM 노드들을 DOM에 반영한다.
- 리렌더에서의 커밋 단계는 렌더 간 발생한 최소한의 변경 사항들을 DOM에 반영한다.
렌더 단계에서 계산한 변경 사항들을 실제 DOM에 적용할 때 "적용"이라는 것은 DOM 노드를 새로 생성, 수정 또는 삭제해 새로운 컴포넌트 트리와 동기화하는 과정을 의미한다. DOM의 조작이 발생하면 전체 UI를 다시 렌더링 하는 것처럼 보이지만 실제로는 변경된 DOM 노드만 파악해서 최소한의 변경만 실제 DOM에 반영한다.
그렇다면 간략하게 리액트의 랜더링 과정에 대해 알게 되었으니 리액트 피버에 대해 알아보자
리액트 피버
리액트 16에서 새롭게 도입된 재조정 엔진이다. Fiber는 리액트 컴포넌트의 정보를 담고 있는 자바스크립트 객체다.
fiber는 리액트의 작업 단위를 나타내는 특별한 객체로 실제 dom 노드, 컴포넌틔 인스턴스에 대응되며 렌더링 작업을 관리하는데 필요한 정보를 담고 있다. 웹 애플리케이션의 ui는 하나의 트리 구조인데, 리액트는 이 UI 트리의 각 노드에 대해 Fiber 노드를 만들어 관리한다.
function FiberNode(
this: $FlowFixMe,
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
}
instance 속성
- tag: Fiber의 유형을 나타낸다. 숫자값이 들어가는데 함수형 컴포넌트면 FunctionComponent, 클래스 컴포넌트라면 ClassComponent로 표현한다.
- tag에 들어가는 값은 HostRoot(루트 Fiber로 ReactDOM.render()의 컨테이너), HostText(텍스트 노드), HostComponent(div, span 등) 등 다양한 값이 있다.
- key: 리액트에서 컴포넌트를 구분하기 위한 고유 식별자이다.
- elementType: 리액트 엘리먼트의 원본 타입을 저장한다. div 태그라면 'div', 커스텀 컴포넌트 App 컴포넌트라면 App 함수가 저장된다.
- type: 실제 작업에 사용되는 타입이다.
- stateNode: Fiber와 관련된 실제 인스턴스를 가리킨다. DOM 노드, 클래스 컴포넌트의 인스턴스 등이 있다.
Fiber 트리 구조 속성
- return: 부모 Fiber로 작업이 완료된 후 돌아갈 뜻으로 return을 사용한다.
- child: 첫번째 자식 Fiber를 가리킨다.
- sibling: 다음 형제 Fiber를 가리킨다.
- index: 형제들 사이에서의 인덱스 번호다.
- ref: 리액트 ref 객체를 저장한다
상태 관련 속성
- pendingProps: 아직 처리되지 않은 새로운 props를 나타낸다.
- memoizedProps: 마지막 렌더링에 사용된 props를 나타낸다.
- updateQueue: 상태 업데이트, 콜백 등의 대기열이다.
- memoizedState: 마지막 렌더링에 사용된 state이다.
- dependencies: context, events 등의 의존성 정보다.
- mode: Fiber의 동작 모드(Concurrent, Sync 등)를 나타낸다.
Effect 관련 속성
- flags: Fiber에 필요한 작업인 생성, 업데이트, 삭제 등을 표시한다.
- subtreeFlags: 자식들에 대한 작업에 대해 표시한다.
- deletions: 삭제될 자식들의 목록이다.
- lanes: 작업들의 우선순위를 나타낸다. -> startTransition 사용 시 다른 lane에 들어가 우선순위를 낮게 반영
- childLanes: 자식들의 작업 우선순위를 나타낸다.
- alternate: 다른 트리에 대응되는 Fiber이다.
++추가 정보
최신 리액트에는 current 트리와 workInProgress 트리 2가지를 사용한다.
간단하게만 말하자면 현재 화면에 렌더링된 UI를 표현하는 건 current 트리이고, 모든 변경 작업을 하는 작업용 트리는 workInProgress 트리다. 즉, workInProgress 트리에서 작업이 끝나면 이를 current 트리로 바꾸어주게 된다.
이를 통해 현재 UI의 안정성을 보장하고, 불완전한 UI 노출이 방지된다.
-> 이중 버퍼링 기법이라고 한다.
Fiber 도입 이유
이전 리액트에서는 스택 조정자로, 하나의 스택에 렌더링에 필요한 작업이 쌓이면 이 스택에서 꺼내 동기적으로 작업이 이루어졌다. 대규모 컴포넌트 트리 조정 작업이 시작되면 이 작업이 완료될 때까지 메인 스레드가 차단되었다. 이로 인해서 프레임 드랍이 발생하고 사용자 입력에 대한 반응이 지연되었다
++추가 정보
특정 순간에 I/O나 리소스 로드, 연산 처리 등으로 인해 평균 프레임 이하로 떨어져서 끊기는 듯한 현상을 말한다. 예를 들어 60fps는 1초에 60장의 정지 화면을 보여주는데 만약 위와 같은 이유로 평균 프레임 이하로 떨어져서 60장보다 적은 화면을 보여주게 되면 사용자는 화면이 끊기는 현상을 느끼게 된다.
Fiber를 도입하기 전의 문제
- 렌더링 작업 중단 불가
재조정 프로세스가 시작되면 전체 컴포넌트 트리를 순회해야했고, 이 과정을 중간에 중단할 수 없었다.
- 우선순위 처리 불가능
모든 업데이트가 동일한 우선순위로 처리되어, 긴급한 업데이트와 중요한 업데이트를 구분할 수 없었다.
- 비동기 렌더링 지원 X
렌더링 작업을 여러 프레임에 걸쳐 분산시킬 수 없어 복잡한 업데이트 시 성능 문제가 발생했다.
Fiber를 도입하고 달라진 점
- 작업 분할과 중단 가능
이전에는 재조정 과정이 한 번에 완료되어야했다. Fiber는 작업을 작은 단위로 나누어 중단하고 다시 시작하는게 가능하다.
++추가정보
이전에는 재귀적으로 처리해서 콜 스택이 비워질 때까지(모든 하위 컴포넌트를 처리할 때까지) 중간에 멈출 수 없었다. Fiber는 이 문제를 해결하기 위해 작업을 나중에 이어할 수 있는 자체 스케줄러를 사용한다.
- 우선 순위 기반 처리
이전에는 모든 업데이트가 동일한 우선순위로 처리되었다. Fiber는 다양한 우선순위 레벨을 지원한다.
- 더 나아진 에러 처리
++추가정보
이전에는 에러가 발생하면 전체 애플리케이션이 영향을 받았다. Fiber는 에러 바운더리를 통해 우아한 실패 처리가 가능하다.
이전에는 에러가 발생하면 한 컴포넌트의 에러가 전체 애플리케이션을 중단시켰다. 그러나 Fiber 아키텍처에서는 각 노드가 자체적 에러 상태를 추적할 수 있다. Fiber는 각 노드의 상태와 생명주기를 독립적으로 관리하기 때문에 가능하다.
작업 분할 프로세스
리액트 Fiber 아키텍처에서는 작업을 작은 단위로 나누어 처리한다고 했다. 이 과정을 좀 더 자세히 알아보았다.
- 리액트는 전체 컴포넌트 트리를 Fiber 노드로 구성된 연결리스트로 변환한다.
- 각 Fiber 노드는 하나의 작업 단위를 나타내고, 다음에 처리할 작업에 대한 참조를 포함한다.
- 연결리스트를 따라가며 각 노드의 작업을 처리한다. 그런데 할당된 작업 시간이 끝나면, 현재 작업을 중단하고 다음 노드 위치를 저장한다.
- 이후 브라우저가 작업이 없다면 저장된 위치부터 작업을 다시 시작한다.
++추가 정보
이전 리액트에서는 스택 조정자 렌더링 시스템으로, 컴포넌트 트리를 순회할 때 각 컴포넌트 처리를 콜 스택에 추가했고 하위 컴포넌트가 처리될 때까지 이 프로세스는 계속 되었다.
이 방식은 렌더링 프로세스를 중단할 수 없었고, 브라우저 메인 스레드를 차단하는 방식이었기 때문에 사용자 입력이나 애니메이션에 즉각적 반응을 할 수 없게 만들었다.
리액트 피버와 함께 다시 랜더링 살펴보기
> 초기 렌더링

1. 초기 마운트
ReactDOM.createRoot(container).render(<App />);
위 코드 호출 시 리액트는 애플리케이션의 기반 구조를 설정한다.
- createRoot를 통해 fiberRoot 노드를 생성한다.
- HostRoot Fiber 노드가 생성된다.
[DOM 레벨]
div#root (containerInfo)
│
[React 레벨]
FiberRoot
│
├─ current → HostRootFiber
│ │
│ ├─ stateNode → FiberRoot 참조
│ │
│ └─ child → AppFiber
│ │
└─ finishedWork └─ 하위 컴포넌트
FiberRoot
리액트 인스턴스 전체를 관리하는 컨테이너다. 렌더링 스케줄링과 우선순위를 관리하고, 현재 화면에 보이는 트리와 작업 중인 트리인 current 트리와 workInProgress 트리를 추적한다. 전역적인 상태 관리를 한다.
HostRootFiber
실제 렌더링 트리의 시작점이다. 첫 번째 실제 Fiber 노드로, 컴포넌트의 부모 역할을 하며, 업데이트의 시작점이다.
2. 렌더링 프로세스 시작
render()가 호출되면 리액트는 렌더링 프로세스를 시작한다.
이때 2가지의 중요한 작업이 이루어진다.
- 최초의 renderRootSync/Concurrent를 실행한다. 동기 모드로 실행할지 동시 모드로 실행할지 결정한다.
- workInProgress 트리 생성을 시작한다.
renderRootSync와 renderRootConcurrent
리액트 렌더링 모드를 나타내는데, Sync 모드는 전통적 방식으로 렌더링이 시작되면 완료될 때까지 중단 없이 진행된다. Concurrent 모드는 렌더링 작업을 작은 단위로 나누어 처리하고 우선순위가 높은 작업이 들어오면 현재 렌더링을 잠시 중단하고 나중에 재개할 수 있다.
3. 컴포넌트 처리 단계
beginWork 단계에서 리액트는 컴포넌트 트리를 위에서 아래로 순회하며 각 컴포넌트를 처리한다.
각 컴포넌트에 대해서
- 컴포넌트의 렌더링 함수를 실행해 리액트 엘리먼트를 가져온다.
- 해당 엘리먼트를 기반으로 새로운 Fiber 노드를 생성한다.
- 자식 컴포넌트들에 대한 Fiber를 생성하고 연결한다.
++추가 정보
이미 생성된 workInProgress 트리의 노드를 처리하는 함수다. 트리를 순회하며 각 노드의 자식을 처리하고 필요한 업데이트를 수행한다. 렌더링 로직을 실행하고 자식 Fiber 노드를 생성하거나 업데이트한다.
4. DOM 준비 단계
completeWork 단계에서는 실제 DOM 업데이트를 위한 준비가 이루어진다.
- 각 Fiber 노드에 대응하는 DOM 노드가 생성된다.
- className, style 등의 다양한 속성이 설정된다.
- onClick 등의 이벤트 리스너가 연결된다.
- 텍스트 콘텐츠가 설정된다.
++추가 정보
completeWork 단계에서는 DOM 업데이트 준비를 하는데, 실제 DOM을 수정하는 것이 아닌 커밋 단계에서 필요한 모든 정보를 수집한다.
1. Fiber 노드에 해당하는 DOM 노드의 인스턴스를 생성하고 Fiber 노드의 stateNode 속성에 저장한다.
2. DOM 노드에 필요한 속성인 className, style, 이벤트 리스너 등을 계산한다.
3. 필요한 DOM 조작을 나타내는 Update, Delete와 같은 flags를 설정한다.
++추가 정보
beginWork와 completeWork의 순서
beginWork는 트리를 아래로 순회하며 컴포넌트 변경사항을 계산한다. 아래로 순회하는 이유는 부모 컴포넌트의 변경이 자식 컴포넌트에 어떤 영향을 미치는지 파악하기 위해서다.
completeWork는 트리를 위로 올라가며 실행된다. 이는 자식 컴포넌트 변경사항이 모두 처리된 후 부모 컴포넌트의 DOM 업데이트를 준비하는 것이 효율적이기 때문이다.
- 한 번에 하나의 경로만 메모리에 유지하며, 효율적으로 메모리를 사용한다.
- 부모에서 자식으로 데이터 흐름을 보장한다.
- 변경사항을 배치로 처리하여 최적화된 DOM 업데이트가 가능하다.
5. 최종 커밋 단계
commitRoot 단계에서는 모든 준비된 변경사항이 실제 DOM에 적용된다.
- 준비된 DOM 노드들이 실제 문서에 삽입된다.
- ref가 설정된다.
- useLayoutEffect 훅이 실행된다.
- useEffect 등의 부수 효과가 실행된다.
> 리랜더링
1. 리렌더링 트리거
리액트의 리렌더링은 아래와 같은 이유로 발생한다.
- 상태(state) 변경
- 속성(props) 변경
- 부모 컴포넌트의 리렌더링
useState를 사용해 상태를 변경하거나, 부모 컴포넌트로부터 새로운 props를 받거나 상위 컴포넌트의 리렌더링 등의 이유가 있다.
2. 업데이트 처리 준비
리액트는 발생한 업데이트를 즉시 처리하지 않고, 우선 업데이트 큐에 추가한다. 이때 각 업데이트에는 우선순위가 할당된다. 예를 들어 사용자 입력에 의한 업데이트는 높은 우선순위를, 데이터 가져오기 같은 백그라운드 작업은 낮은 우선순위를 받게 된다.
이때 진행되는 일을 정리하면 아래와 같다.
- 발생한 업데이트를 업데이트 큐에 추가한다.
- 우선순위를 설정하고 렌더링 스케줄링을 한다.
3. 작업용 트리 준비
현재 화면에 표시되는 UI를 나타내는 current 트리가 이미 존재하므로, 리액트는 이 트리를 기반으로 새로운 workInProgress 트리를 생성한다.
완전히 새로운 트리를 만드는게 아니라 current 트리를 복제하여 시작한다.
4. 변경사항 계산
beginWork 단계에서 리액트는 트리를 순회하며 각 컴포넌트의 변경사항을 확인한다.
Diffing 알고리즘을 사용하여 최소한의 필요한 업데이트만을 계산한다. 예를 들어 props가 변경되지 않은 컴포넌트는 기존 결과를 재사용한다.
5. DOM 업데이트 준비
completeWork 단계에서는 실제 DOM 업데이트를 위한 준비가 이루어진다.
- 각 Fiber 노드에 대해 필요한 DOM 업데이트를 나타내는 flags가 설정된다.
- 텍스트가 변경되었다면 flags는 Update, 새로운 요소가 추가되었다면 flags는 Placement가 설정된다.
++추가 정보
Fiber 노드에 대해서 노드를 새로 생성해야하는지, 업데이트해야 하는지, 삭제해야 하는지 등을 flags에 설정한다.
6. 변경사항 적용
commitRoot 단계에서 모든 준비된 사항이 실제 DOM에 적용된다. 이전 단계에서 설정된 flags를 기반으로 필요한 DOM 업데이트만 수행된다.
또한 useEffect와 같은 효과들도 이 단계에서 필요한 경우에만 실행된다.
출처
리액트의 Fiber를 모르는 Chill guy일 때
Fiber 아키텍처를 도입한 후, 리액트에서의 초기 렌더링과 리렌더링은 어떤 과정으로 이루어지는지 공부해보았다.
velog.io
React의 렌더 단계와 커밋 단계
리액트 렌더링 단계라고 하면 렌더 단계(Render Phase)와 커밋 단계(Commit Phase)가 있는데요, 렌더 단계에서는 화면에 그릴 것들을 "파악"하고 커밋 단계에서는 직전에 파악한 것들을 화면에 "적용"해
www.moonkorea.dev
https://ko.react.dev/learn/understanding-your-ui-as-a-tree
트리로서 UI 이해하기 – React
The library for web and native user interfaces
ko.react.dev
'코딩 정보 > React' 카테고리의 다른 글
| 선언형으로 에러 관리하기 (0) | 2026.07.11 |
|---|---|
| 스크롤 락에 대해 알아보자 (1) | 2026.06.15 |
| [React] useOptimistic을 통해 낙관적 업데이트를 구현해보자 (7) | 2025.08.04 |
| [React] useLayoutEffect 와 useEffect (2) | 2025.08.02 |
| [React] stompjs, sockjs를 통해 소켓 통신 하기 (0) | 2025.04.19 |