가상 DOM을 알아보기에 앞서 DOM(Document Object Model)이란 무엇인지 먼저 살펴보자.
DOM은 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담고 있다.
브라우저가 웹사이트 접근 요청을 받고 화면을 그리는 과정은 다음과 같다.
display: none
과 같은 요소는 방문하지 않는다.https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko
브라우저가 웹페이지를 렌더링하는 과정은 매우 복잡하고 많은 비용이 든다.
특정 요소의 색상이 변경되는 경우를 살펴보자, 이 경우는 리페인팅만 일어나므로 비교적 빠르게 처리 된다.
이번에는 특정 요소의 노출 여부나 사이즈가 변경되는 경우를 살펴보자. 이 경우에는 리플로우가 일어나고, 이는 반드시 이후에 리페인팅이 발생하기 때문에 더 많은 비용이 든다. 또한 요소가 많은 자식을 가지고 있는 경우 자식 요소들도 덩달아 변경돼야 하기 때문에 더 많은 비용이 든다.
요즘 웹은 하나의 인터랙션으로 인해 내부의 DOM 여러 가지가 변경되는 경우가 흔하다. 이러한 인터랙션에 따라 DOM의 모든 변경 사항을 추적하는 것은 개발자 입장에서 너무 힘든 일이다. 대부분의 경우 결과적으로 만들어지는 DOM 하나만 알고 싶을 것이다.
이러한 문제점을 해결하기 위해 탄생한 것이 가상 DOM이다. 가상 DOM은 실제 브라우저의 DOM이 아닌 리액트가 관리하는 가상의 DOM을 의미한다. 가상 DOM은 웹페이지가 표시해야 할 DOM을 일단 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료됐을 때 실제 브라우저에 반영한다.
📌 가상 DOM은 무조건 일반 DOM보다 빠르다?
- 리액트 개발자인 댄 아브라모프는 사실이 아니라고 부정했다. 무조건 빠른 것이 아니라. 대부분의 상황에서 충분히 빠르다는 것이다.
리액트에서 가상 DOM과 렌더링 과정을 최적하게 해주는 것은 리액트 파이버(React Fiber)다.
리액트 파이버는 리액트에서 관리하는 자바스크립트 객체다. 파이버는 파이버 재조정자(fiber reconciler)가 관리하며, 이는 가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하여 이 둘 사이에 차이가 있으면 변경에 관련된 정보를 기준으로 화면에 렌더링을 요청하는 역할을 한다.
재조정(reconciliation)은 리액트에서 어떤 부분을 새롭게 렌더링 해야하는지 비교하는 알고리즘이라고 이해하면 된다.
리액트 파이버는 리액트 앱에서 발생하는 애니메이션, 레이아웃, 인터랙션에 대한 올바른 결과물을 만드는 반응성 문제를 해결하는 것을 목표로 한다. 이를 위해 다음과 같은 일들을 수행한다.
그리고 이러한 과정들이 모두 비동기로 일어난다.
파이버 트리는 리액트 내부에 두 개가 존재한다. 하나는 현재 모습을 담은 파이버 트리, 다른 하나는 작업 중인 상태를 나타내는 workInProgress
트리다. 리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 workInProgress
트리를 현재 트리로 바꿔 버린다. 이러한 기술을 더블 버퍼링이라고 한다.
리액트에서 불완전한 트리를 보여주지 않기 위해 더블 버퍼링 기법을 쓰는데, 이 더블 버퍼링은 커밋 단계에서 수행된다.
beginWork()
함수를 실행해 파이버 작업을 수행한다. 더 이상 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작된다.completeWork()
함수를 실행해 파이버 작업을 완료한다.return
으로 돌아가 작업이 완료됐음을 알린다.아래 예시 코드를 도식화 하면 다음과 같다.
function App() { // App
return (
<div className="wrapper"> // W
<div className="list"> // L
<div className="list_item">List item A</div> // LA
<div className="list_item">List item B</div> // LB
</div>
<div className="section"> // S
<button>Add</button> // SB
<span>No. of items: 2</span> // SS
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root')); // HostRoot
트리가 생성됐다. 이제 setState등으로 업데이트가 발생하면 어떻게 될까? 앞서 만든 current 트리가 존재하고, workInProgress트리를 다시 빌드 하기 시작한다. 이 빌드 과정은 앞선 과정과 동일하다. 최초 렌더링 시에는 모든 파이버를 새롭게 만들지만, 이제는 파이버가 이미 존재하므로 되도록 새로 생성하지 않고 기존 파이버를 사용한다.
리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이뤄진다.
그러나 실제 DOM에 반영하는 것은 동기적으로 일어나야 하고, 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있는 가능성이 높으므로 이러한 작업을 메모리상에서 먼저하고 최종적인 결과물만 적용하는 것이다.
참조
- 모던 리액트 Deep Dive
- https://www.velotio.com/engineering-blog/react-fiber-algorithm