[1.DOM과 브라우저 렌더링 과정]
[1-1. DOM이란]
문서 객체 모델(Document Object Model)은 XML, HTML 문서의 각 항목을 계층으로 표현하여 생성, 변형, 삭제할 수 있도록 돕는 인터페이스이다. W3C(World Wide Web Consortium)의 표준이다.
DOM은 웹페이지의 콘텐츠와 구조를 어떻게 구현해야 하는지에 대한 정보를 담은 인터페이스로, 트리 구조로 이루어져 있다.
[1-2. 브라우저 렌더링]
브라우저 렌더링은 다음과 같은 순서로 이루어진다.
- URL의 호스트 이름이 DNS를 통해 IP 주소로 변경되고, 그 주소의 서버에 요청을 전송한다.
- 서버는 HTML 파일을 전송한다.
- HTML/XML 파서는 전달받은 HTML을 파싱해 DOM 트리를 생성한다.
- 파싱 과정에서 외부 리소스 로드 태그(link, img, script...) 등을 인식하면 파싱을 일시 중지하고 외부 리소스를 서버로 요청한다.
- link 태그의 href에 지정된 CSS파일을 서버에 요청한 후 로드된 CSS 파일을 파싱해 CSSOM 트리를 생성한다. 이후 파싱을 완료하면 중단된 지점부터 다시 HTML 파싱을 재개한다.
- 생성된 DOM을 순회하며 시각적으로 표시되는 노드를 골라 스타일정보를 노드에 적용하며 Render 트리를 생성한다.

- Render 트리를 기초로 레이아웃 과정을 통해 그려질 노드와 그 스타일, 크기를 계산한다.
- 페인팅을 통해 Render 트리를 호출해 화면에 픽셀을 렌더링한다.
[2. 가상 DOM의 탄생 배경]
[2-1. VanillaJS의 단점]
SPA(Single Page Application)에서는 하나의 페이지에서 모든 작업이 발생한다.
따라서 추가 렌더링 작업이 필요한 경우가 많다.
따라서 사용자의 인터랙션에 따라 리플로우, 리페인트가 발생하며 많은 자원을 소모하게 된다.
특히 라우팅이 변경되는 경우, 대부분의 요소를 삭제하고 다시 삽입, 계산 작업을 수행해야 한다.
- 리플로우: 레이아웃을 다시 계산하는 것. 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우 실행된다.
- 리페인트: 재결합된 렌더 트리를 기반으로 다시 페인팅하는 것.
반드시 리플로우와 리페인트가 모두 실행되지는 않는다.
레이아웃 변경이 있는 디자인 변화가 일어난 경우: JS, CSS 파싱 -> 렌더 트리 구축 -> 레이아웃 -> 리페인트 -> 레이어 업데이트 -> 합성
레이아웃 변경이 없는 디자인 변화가 일어난 경우: JS, CSS 파싱 -> 렌더 트리 구축 -> 리페인트 -> 레이어 업데이트 -> 합성
레이아웃, 리페인트 둘 모두 필요없는 경우(?): JS, CSS 파싱 -> 렌더 트리 구축 -> 레이어 업데이트 -> 합성
[2-2. 가상 DOM]
따라서 리액트는 가상 DOM(Virtual DOM)이라는 개념을 도입해 연산을 줄였다. (현재는 valueUI라는 명칭을 사용하기를 권장)
가상 DOM이란 JS의 객체 형태로 메모리에 저장된 실제 DOM의 복사본으로, 자바스트립트는 가상 DOM에서 모든 연산을 끝마친 후 한 번에 실제 브라우저의 DOM에 업데이트한다.

브라우저의 DOM 조작보다 가상 DOM을 조작하는 속도가 빠른 것은 아니다.
하지만 DOM 변경에 대한 준비가 완료되었을 때 한 번에 브라우저의 실제 DOM에 업데이트하면서, 한 노드의 레이아웃 변경이 다른 노드의 레이아웃에 연쇄적으로 영향을 끼치면서 여러 번 리플로우가 실행되는 일 등을 막을 수 있다.
[3. 가상 DOM을 위한 아키텍쳐, 리액트 파이버]
[3-1. 기존 리액트 동작 방식의 문제점]
리액트 파이버는 리액트 웹 애플리케이션에서 발생하는 애니메이션, 레이아웃, 사용자 인터랙션 등에서 반응성 문제를 해결하기 위해 고안되었다.
리액트 15까지는 스택 방식으로 작동하는 reconciler(재조정자)를 사용했다.

위의 예시처럼 각 노드를 순회할 때 각 노드를 스택에 push하고 순회가 끝난 노드를 pop하는 방식을 사용했는데, 위 방식의 단점은
- 한번 가상 DOM 트리를 순회하기 시작하면, 순회가 끝날 때까지 다른 작업을 수행하지 못한다.(JS가 싱글 스레드 언어이기 때문)
- 위의 이유 때문에, 자바스크립트의 비동기 처리 기능을 활용하지 못한다. (각 node는 트리 구조로 이루어져 있으므로 작업 우선순위, 다음 작업 등을 알 수 없어 비동기 처리 불가)
[3-2. 리액트 파이버]
리액트 16부터는 위 문제를 해결하기 위해 파이버라는 개념을 도입했다.
파이버(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; // 함수 또는 클래스 컴포넌트 자체(div 등)
this.stateNode = null;
// Fiber
this.return = null; // 각 파이버가 작업을 수행한 후 반환할 최신 정보.
this.child = null;
this.sibling = null; // 파이버는 기존의 일반 트리 구조 대신 singly-linked list 형식을 사용하므로 sibling 정보도 포함한다.
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps;
this.memoizedProps = null; // pendingProps와 memorizedProps이 같으면 이전의 결과를 재사용할 수 있다는 의미이므로 불필요한 연산을 줄일 수 있다.
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; // cloneFiber() 함수에 의해 생성되며, 해당 fiber에 대응하는 반대 트리의 fiber를 가리킨다..
// ...
(*공부한 것들에만 주석을 달아놓았고, 추후 추가 예정)
파이버를 사용함으로서 리액트는
- 작업을 작은 단위로 분할하고 우선순위를 정할 수 있다.
- 작업을 잠시 멈추고, 멈춘 곳에서 다시 시작할 수 있다.
- 작업을 재사용하거나 버릴 수 있다.
파이버는 state의 변경, 생명주기 메서드 실행, DOM의 변경 때 작업을 시작한다.
이전 리액트 구조에서와 달리, 파이버는 작업을 바로 수행하거나 우선 순위에 따라 스케줄링한다.(애니메이션, 사용자 입력 등 높은 우선순위의 작업이 필요하면 잠시 중단했다가 그 부분부터 이어서 작업한다)
[3-3. 파이버 트리]

1. 리액트는 웹 애플리케이션이 실행될 때 두 개의 파이버 트리 공간을 메모리에 할당한다.
- 현재 상태를 나타내는 current 트리, 작업 중 상태를 나타내는 workInProgress 트리
2. current 트리에 현재의 DOM 트리를 빌드한다.
3. 업데이트 사항이 생기면 current 트리를 기반으로 workInProgress 트리를 빌드한다.
4. workInProgress 트리의 모든 파이버 작업이 끝나면, root 노드의 current가 workInProgress의 최상위 노드를 가리키면서 current 트리가 된다.
5. 위 과정을 반복한다.
[3-4. 파이버 작업 순서]
1. beginWork() 함수를 실행해 파이버 작업을 수행한다. 각 노드를 트리 구조로 순회하며, 자식이 없는 파이버를 만날때까지 각 파이버는 beginWork()를 실행한다.
2. 자식이 없는 파이버는 completeWork를 실행한다.
3. 형제가 있다면 형제로 넘어가서 반복한다.
4. 작업이 끝나면 각 파이버는 return을 통해 작업 결과를 반환하고, 루트 노드까지 completeWork가 실행되면 최종적으로 commitWork를 실행해 변경사항을 비교한다.
5. 업데이트가 끝나면 current 트리를 workInProgress 트리로 바꾸어 모든 변경사항을 DOM에 반영한다.
[4. 파이버와 가상 DOM]
이를 정리하자면, 리액트 컴포넌트에 대해 1:1로 정보를 가지고 있는 것이 파이버이다.
파이버는 각각의 작업 자체를 나타내며, 리액트 아키텍처 내부에서 비동기로 이루어진다.
실제 DOM에 반영하는 작업은 동기적으로 수행되어야 하며, 그 과정에서 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있다.
이러한 이유로 인해 메모리에서 먼저 DOM의 상태를 수정한 뒤 한 번에 실제 DOM에 업데이트 하는 작업을 가상 DOM이 수행한다.
[1.DOM과 브라우저 렌더링 과정]
[1-1. DOM이란]
문서 객체 모델(Document Object Model)은 XML, HTML 문서의 각 항목을 계층으로 표현하여 생성, 변형, 삭제할 수 있도록 돕는 인터페이스이다. W3C(World Wide Web Consortium)의 표준이다.
DOM은 웹페이지의 콘텐츠와 구조를 어떻게 구현해야 하는지에 대한 정보를 담은 인터페이스로, 트리 구조로 이루어져 있다.
[1-2. 브라우저 렌더링]
브라우저 렌더링은 다음과 같은 순서로 이루어진다.
- URL의 호스트 이름이 DNS를 통해 IP 주소로 변경되고, 그 주소의 서버에 요청을 전송한다.
- 서버는 HTML 파일을 전송한다.
- HTML/XML 파서는 전달받은 HTML을 파싱해 DOM 트리를 생성한다.
- 파싱 과정에서 외부 리소스 로드 태그(link, img, script...) 등을 인식하면 파싱을 일시 중지하고 외부 리소스를 서버로 요청한다.
- link 태그의 href에 지정된 CSS파일을 서버에 요청한 후 로드된 CSS 파일을 파싱해 CSSOM 트리를 생성한다. 이후 파싱을 완료하면 중단된 지점부터 다시 HTML 파싱을 재개한다.
- 생성된 DOM을 순회하며 시각적으로 표시되는 노드를 골라 스타일정보를 노드에 적용하며 Render 트리를 생성한다.

- Render 트리를 기초로 레이아웃 과정을 통해 그려질 노드와 그 스타일, 크기를 계산한다.
- 페인팅을 통해 Render 트리를 호출해 화면에 픽셀을 렌더링한다.
[2. 가상 DOM의 탄생 배경]
[2-1. VanillaJS의 단점]
SPA(Single Page Application)에서는 하나의 페이지에서 모든 작업이 발생한다.
따라서 추가 렌더링 작업이 필요한 경우가 많다.
따라서 사용자의 인터랙션에 따라 리플로우, 리페인트가 발생하며 많은 자원을 소모하게 된다.
특히 라우팅이 변경되는 경우, 대부분의 요소를 삭제하고 다시 삽입, 계산 작업을 수행해야 한다.
- 리플로우: 레이아웃을 다시 계산하는 것. 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우 실행된다.
- 리페인트: 재결합된 렌더 트리를 기반으로 다시 페인팅하는 것.
반드시 리플로우와 리페인트가 모두 실행되지는 않는다.
레이아웃 변경이 있는 디자인 변화가 일어난 경우: JS, CSS 파싱 -> 렌더 트리 구축 -> 레이아웃 -> 리페인트 -> 레이어 업데이트 -> 합성
레이아웃 변경이 없는 디자인 변화가 일어난 경우: JS, CSS 파싱 -> 렌더 트리 구축 -> 리페인트 -> 레이어 업데이트 -> 합성
레이아웃, 리페인트 둘 모두 필요없는 경우(?): JS, CSS 파싱 -> 렌더 트리 구축 -> 레이어 업데이트 -> 합성
[2-2. 가상 DOM]
따라서 리액트는 가상 DOM(Virtual DOM)이라는 개념을 도입해 연산을 줄였다. (현재는 valueUI라는 명칭을 사용하기를 권장)
가상 DOM이란 JS의 객체 형태로 메모리에 저장된 실제 DOM의 복사본으로, 자바스트립트는 가상 DOM에서 모든 연산을 끝마친 후 한 번에 실제 브라우저의 DOM에 업데이트한다.

브라우저의 DOM 조작보다 가상 DOM을 조작하는 속도가 빠른 것은 아니다.
하지만 DOM 변경에 대한 준비가 완료되었을 때 한 번에 브라우저의 실제 DOM에 업데이트하면서, 한 노드의 레이아웃 변경이 다른 노드의 레이아웃에 연쇄적으로 영향을 끼치면서 여러 번 리플로우가 실행되는 일 등을 막을 수 있다.
[3. 가상 DOM을 위한 아키텍쳐, 리액트 파이버]
[3-1. 기존 리액트 동작 방식의 문제점]
리액트 파이버는 리액트 웹 애플리케이션에서 발생하는 애니메이션, 레이아웃, 사용자 인터랙션 등에서 반응성 문제를 해결하기 위해 고안되었다.
리액트 15까지는 스택 방식으로 작동하는 reconciler(재조정자)를 사용했다.

위의 예시처럼 각 노드를 순회할 때 각 노드를 스택에 push하고 순회가 끝난 노드를 pop하는 방식을 사용했는데, 위 방식의 단점은
- 한번 가상 DOM 트리를 순회하기 시작하면, 순회가 끝날 때까지 다른 작업을 수행하지 못한다.(JS가 싱글 스레드 언어이기 때문)
- 위의 이유 때문에, 자바스크립트의 비동기 처리 기능을 활용하지 못한다. (각 node는 트리 구조로 이루어져 있으므로 작업 우선순위, 다음 작업 등을 알 수 없어 비동기 처리 불가)
[3-2. 리액트 파이버]
리액트 16부터는 위 문제를 해결하기 위해 파이버라는 개념을 도입했다.
파이버(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; // 함수 또는 클래스 컴포넌트 자체(div 등)
this.stateNode = null;
// Fiber
this.return = null; // 각 파이버가 작업을 수행한 후 반환할 최신 정보.
this.child = null;
this.sibling = null; // 파이버는 기존의 일반 트리 구조 대신 singly-linked list 형식을 사용하므로 sibling 정보도 포함한다.
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps;
this.memoizedProps = null; // pendingProps와 memorizedProps이 같으면 이전의 결과를 재사용할 수 있다는 의미이므로 불필요한 연산을 줄일 수 있다.
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; // cloneFiber() 함수에 의해 생성되며, 해당 fiber에 대응하는 반대 트리의 fiber를 가리킨다..
// ...
(*공부한 것들에만 주석을 달아놓았고, 추후 추가 예정)
파이버를 사용함으로서 리액트는
- 작업을 작은 단위로 분할하고 우선순위를 정할 수 있다.
- 작업을 잠시 멈추고, 멈춘 곳에서 다시 시작할 수 있다.
- 작업을 재사용하거나 버릴 수 있다.
파이버는 state의 변경, 생명주기 메서드 실행, DOM의 변경 때 작업을 시작한다.
이전 리액트 구조에서와 달리, 파이버는 작업을 바로 수행하거나 우선 순위에 따라 스케줄링한다.(애니메이션, 사용자 입력 등 높은 우선순위의 작업이 필요하면 잠시 중단했다가 그 부분부터 이어서 작업한다)
[3-3. 파이버 트리]

1. 리액트는 웹 애플리케이션이 실행될 때 두 개의 파이버 트리 공간을 메모리에 할당한다.
- 현재 상태를 나타내는 current 트리, 작업 중 상태를 나타내는 workInProgress 트리
2. current 트리에 현재의 DOM 트리를 빌드한다.
3. 업데이트 사항이 생기면 current 트리를 기반으로 workInProgress 트리를 빌드한다.
4. workInProgress 트리의 모든 파이버 작업이 끝나면, root 노드의 current가 workInProgress의 최상위 노드를 가리키면서 current 트리가 된다.
5. 위 과정을 반복한다.
[3-4. 파이버 작업 순서]
1. beginWork() 함수를 실행해 파이버 작업을 수행한다. 각 노드를 트리 구조로 순회하며, 자식이 없는 파이버를 만날때까지 각 파이버는 beginWork()를 실행한다.
2. 자식이 없는 파이버는 completeWork를 실행한다.
3. 형제가 있다면 형제로 넘어가서 반복한다.
4. 작업이 끝나면 각 파이버는 return을 통해 작업 결과를 반환하고, 루트 노드까지 completeWork가 실행되면 최종적으로 commitWork를 실행해 변경사항을 비교한다.
5. 업데이트가 끝나면 current 트리를 workInProgress 트리로 바꾸어 모든 변경사항을 DOM에 반영한다.
[4. 파이버와 가상 DOM]
이를 정리하자면, 리액트 컴포넌트에 대해 1:1로 정보를 가지고 있는 것이 파이버이다.
파이버는 각각의 작업 자체를 나타내며, 리액트 아키텍처 내부에서 비동기로 이루어진다.
실제 DOM에 반영하는 작업은 동기적으로 수행되어야 하며, 그 과정에서 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있다.
이러한 이유로 인해 메모리에서 먼저 DOM의 상태를 수정한 뒤 한 번에 실제 DOM에 업데이트 하는 작업을 가상 DOM이 수행한다.