가상 DOM과 리액트 파이버

상현·2024년 6월 20일
2

React

목록 보기
24/24
post-thumbnail

1. DOM과 브라우저 렌더링 과정

가상 DOM을 알아보기에 앞서 DOM(Document Object Model)이란 무엇인지 먼저 살펴보자.

DOM은 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담고 있다.

브라우저가 웹사이트 접근 요청을 받고 화면을 그리는 과정은 다음과 같다.

  1. 브라우저가 사용자가 요청한 주소를 방문해 HTML 파일을 요청한다.
  2. 브라우저의 렌더링 엔진이 HTML을 파싱해 DOM 트리를 만든다.
  3. 2번 과정에서 CSS 파일을 만날 경우 CSS 파일도 요청한다.
  4. 렌더링 엔진은 CSS도 파싱해 CSS 노드로 구성된 트리 CSSOM을 만든다.
  5. 브라우저는 2번에서 만든 DOM 노드를 순회하는데, 사용자 눈에 보이는 노드만 방문한다. 즉, display: none과 같은 요소는 방문하지 않는다.
  6. 눈에 보이는 노드를 대상으로 해당 노드에 대한 CSSOM 정보를 찾고 스타일을 적용한다. 노드에 CSS를 적용하는 과정은 크게 2가지로 나뉜다.
    • 레이아웃(layout, reflow): 각 노드가 브라우저 화면에 정확히 어느 좌표에 나타나야 하는지 계산하는 과정 레이아웃 과정 이후에는 반드시 페인팅 과정을 거친다
    • 페인팅(painting): 레이아웃 단계를 거친 후 색과 같은 실제 유효한 모습을 그리는 과정

https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko

2. 가상 DOM의 탄생 배경

브라우저가 웹페이지를 렌더링하는 과정은 매우 복잡하고 많은 비용이 든다.

특정 요소의 색상이 변경되는 경우를 살펴보자, 이 경우는 리페인팅만 일어나므로 비교적 빠르게 처리 된다.

이번에는 특정 요소의 노출 여부나 사이즈가 변경되는 경우를 살펴보자. 이 경우에는 리플로우가 일어나고, 이는 반드시 이후에 리페인팅이 발생하기 때문에 더 많은 비용이 든다. 또한 요소가 많은 자식을 가지고 있는 경우 자식 요소들도 덩달아 변경돼야 하기 때문에 더 많은 비용이 든다.

요즘 웹은 하나의 인터랙션으로 인해 내부의 DOM 여러 가지가 변경되는 경우가 흔하다. 이러한 인터랙션에 따라 DOM의 모든 변경 사항을 추적하는 것은 개발자 입장에서 너무 힘든 일이다. 대부분의 경우 결과적으로 만들어지는 DOM 하나만 알고 싶을 것이다.

이러한 문제점을 해결하기 위해 탄생한 것이 가상 DOM이다. 가상 DOM은 실제 브라우저의 DOM이 아닌 리액트가 관리하는 가상의 DOM을 의미한다. 가상 DOM은 웹페이지가 표시해야 할 DOM을 일단 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료됐을 때 실제 브라우저에 반영한다.

📌 가상 DOM은 무조건 일반 DOM보다 빠르다?

  • 리액트 개발자인 댄 아브라모프는 사실이 아니라고 부정했다. 무조건 빠른 것이 아니라. 대부분의 상황에서 충분히 빠르다는 것이다.

3. 리액트 파이버

리액트에서 가상 DOM과 렌더링 과정을 최적하게 해주는 것은 리액트 파이버(React Fiber)다.

리액트 파이버란?

리액트 파이버는 리액트에서 관리하는 자바스크립트 객체다. 파이버는 파이버 재조정자(fiber reconciler)가 관리하며, 이는 가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하여 이 둘 사이에 차이가 있으면 변경에 관련된 정보를 기준으로 화면에 렌더링을 요청하는 역할을 한다.

재조정(reconciliation)은 리액트에서 어떤 부분을 새롭게 렌더링 해야하는지 비교하는 알고리즘이라고 이해하면 된다.

리액트 파이버는 리액트 앱에서 발생하는 애니메이션, 레이아웃, 인터랙션에 대한 올바른 결과물을 만드는 반응성 문제를 해결하는 것을 목표로 한다. 이를 위해 다음과 같은 일들을 수행한다.

  • 작업을 작은 단위로 분할하고 우선순위를 매긴다.
  • 이러한 작업을 일시 중지하고 다시 시작할 수 있다.
  • 이전에 했던 작업을 재사용하거나 폐기할 수 있다.

그리고 이러한 과정들이 모두 비동기로 일어난다.

리액트 파이버 트리

파이버 트리는 리액트 내부에 두 개가 존재한다. 하나는 현재 모습을 담은 파이버 트리, 다른 하나는 작업 중인 상태를 나타내는 workInProgress 트리다. 리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 workInProgress트리를 현재 트리로 바꿔 버린다. 이러한 기술을 더블 버퍼링이라고 한다.

리액트에서 불완전한 트리를 보여주지 않기 위해 더블 버퍼링 기법을 쓰는데, 이 더블 버퍼링은 커밋 단계에서 수행된다.

파이버의 작업 순서

  1. 리액트는 beginWork() 함수를 실행해 파이버 작업을 수행한다. 더 이상 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작된다.
  2. 1번 작업이 끝나면 completeWork() 함수를 실행해 파이버 작업을 완료한다.
  3. 형제가 있다면 형제로 넘어간다.
  4. 2, 3 번이 모두 끝나면 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트리를 다시 빌드 하기 시작한다. 이 빌드 과정은 앞선 과정과 동일하다. 최초 렌더링 시에는 모든 파이버를 새롭게 만들지만, 이제는 파이버가 이미 존재하므로 되도록 새로 생성하지 않고 기존 파이버를 사용한다.

4. 파이버와 가상 DOM

리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이뤄진다.

그러나 실제 DOM에 반영하는 것은 동기적으로 일어나야 하고, 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있는 가능성이 높으므로 이러한 작업을 메모리상에서 먼저하고 최종적인 결과물만 적용하는 것이다.

참조

profile
프론트엔드 개발자 🧑🏻‍💻 https://until.blog/@love

0개의 댓글