가상 DOM과 리액트 파이버

Date
Created
Apr 28, 2024 09:30 AM
Tags
2장
리액트의 특징으로 가장 많이 언급되는 부분은 실제 DOM이 아닌 가상 DOM을 운영한다는 점.

DOM과 브라우저 렌더링 과정

DOM(Document Object Model) 이란 무엇인가?
DOM은 웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 가지고 있음.
  1. 브라우저가 사용자 요청 주소를 방문해 HTML을 다운받음
  1. 브라우저 렌더링 엔진은 HTML을 파싱해 DOM노드로 구성된 트리(DOM)를 만듬
  1. 2번 과정에서 css가 있다면 css 파일도 다운로드함
  1. 브라우저 렌더링 엔진은 css도 파싱해 css노드로 구성된 트리(CSSOM)을 만듬
  1. 브라우저는 2번에서 만든 DOM을 순회하는데 모든 노드를 방문하지 않고 사용자 눈에 보이는 노드만 방문함
    1. disply:none 과 같은 사용자 화면에 보이지 않는 요소는 방문하지 않음 트리를 더 빨리 순회하기 위해
  1. 5번에서 제외된 눈에 보이는 노드를 대상으로 해당 노드에 대한 CSSOM 정보를 찾고 CSS 스타일 정보를 노드에 적용함
      • 레이아웃(layout, reflow): 각 노드가 브라우저 화면의 어느 좌표에 정확히 나타나야 하는지 계산하는 과정, 이 레이아웃을 거치면 반드시 페인팅 과정도 거치게 됨.
      • 페인팅: 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그림
HTML, CSS 파싱, 스타일 계산, 레이아웃, 페인팅 등의 순서로 이루어짐

가상 DOM

실제 브라우저의 DOM이 아닌 리액트가 관리하는 가상의 DOM.
가살 DOM은 웹페이지가 표시해야할 DOM을 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료되면 실제 브라우저의 DOM에 반영함.
DOM 계산을 브라우저가 아닌 메모리에서 계산하는 과정을 한 번 거치게 된다면 실제로 여러번 발생할 렌더링 과정을 최소화 하고 브라우저와 개발자의 부담을 덜 수 있다.
가상 DOM이 항상 일반 DOM보다 무조건 빠르다는 이야기는 오해이며 가상DOM은 대부분의 상황에서 어플리케이션을 만들 수 있을 정도로 충분히 빠르다는말.
 

가상 DOM을 위한 아키텍처, 리액트 파이버

리액트 파이버란?

리액트에서 관리하는 평벙한 자바스크립트 객체.
파이버는 파이버 재조정자가 관리를 하는데, 가상 DOM과 실제 DOM을 비교해 변경사항을 수집하고 이 둘 사이에 차이가 있다면 변경에 관련된 정보를 가지고있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할.
여기서 재조정 이라는 말은 리액트가 어떤 부분을 새롭게 렌더링해야하는지 VDOM과 DOM을 비교하는 알고리즘이라고 생각하면됨
리액트 파이버의 목표는 리액트에서 발생하는 애니메이션, 레이아웃, 사용자 인터랙션에 올바른 결과물을 만드는 반응성 문제를 해결하는 것.
리액트 파이버는 다음의 기능을 제공한다.
  • 작업을 작은 단위로 쪼개고 우선순위를 매김
  • 이런 작업을 일시 중지하고 나중에 다시 시작할 수 있다
  • 이전에 했던 작업을 다시 사용하거나 필요하지 않으면 폐기할 수 있음
중요한 점은 이런 작업이 비동기로 일어나는점
과거에 리액트 조정 알고리즘은 스택으로 이루어져 있었음.
그래서 하나의 스택에 렌더링 작업이 쌓이면 스택이 빌 때 까지 동기적으로 작업이 이루어졌음.
자바스크립트의 특징인 싱글스레드라는 점에 의해 동기 작업은 중단될 수 없고 다른 작업이 수행되고 싶어도 중단할 수 없었다.
예로 검색을 한다고 치면 글자를 칠때마다 여러 fetch가 날아가고 로딩 스피너의 표시 등 여러 작업이 스택에 쌓이게 되는데 이런 작업이 동기적으로 이루어 진다면 시간이 많이 소요되고 심하면 글자 입력에 지연이 생기게됨.
그래서 리액트 팀은 스택 조정자 대신 리액트 파이버라는 개념을 탄생시킨 것.

리액트 파이버의 구현

파이버는 하나의 작업 단위로 구성되어 있음.
리액트는 이런 작업 단위를 하나씩 처리하고 finishedWork()라는 작업으로 마무리하게 됨.
그리고 이런 작업을 커밋해 실제 브라우저 DOM에 가시적인 변경사항을 만듬.
  1. 렌더 단계에서 리액트는 사용자에게 노출되지 않는 모든 비동기 작업을 수행함. 파이버의 작업, 우선순위의 지정 or 작업의 중단 및 폐기 등의 작업이 일어남.
  1. 커밋 단계에서는 앞서 언급한 것 처럼 DOM에 실제 변경 사항을 반영하기 위한 작업. commitWork()가 실행되고 이 과정은 동기적으로 일어나고 중단될 수 없음.
  • tag: 파이버는 하나의 element에 하나가 생기는 1:1 관계를 가짐. 매칭되는것은 리액트 컴포넌트일수도, HTML DOM 노드일수도, 혹은 다른 어떤 것일 수 있음
  • stateNode: 파이버 자체에 대한 참조를 가지고 있으며, 이 참조를 바탕으로 리액트는 파이버와 관련된 상태에 접근함
  • child, sibling, return: 파이버간 관계 개념을 나타내는 속성. 리액트 컴포넌트 트리가 형성되는 것과 동일하게 파이버도 트리 형식을 가지게 됨. 이 트리 형식을 구성하는 데 필요한 정보가 이 속성 내부에 정의된다. 리액트 컴포넌트 트리와 다른점은 children이 없다는 것, 즉 하나의 child만 존재한다.
    • <ul> <li></li> <li></li> <li></li> <ul>
      이런 구조에서 파이버는 항상 첫 번째 자식의 참조로 구성된다. <ul> 파이버의 자식은 첫 번째 <li> 파이버가 되고 나머지는 <li>는 형제 sibling으로 구성되게 됨. 마지막으로 return 은 부모 파이버를 말하며 모든 <li>는 retrun으로 <ul>을 가지게 됨.
  • index: 여러 형제들 사이에 자신의 위치가 몇 번째인지 숫자로 표현함
  • pendingProps: 아직 작업을 미처 처리하지 못한 props
  • updateQueue: 상태 업데이트, 콜백 함수, DOM 업데이트 등 필요한 작업을 담는 큐
  • memorizedState: 함수형 컴포넌트의 훅 목록이 저장됨. useState 뿐만 아니라 모든 훅 리스트가 저장됨
  • alternate: 리액트 파이버 트리와 이어지는 개면. 리액트의 트리는 두 개인데,. alternate는 반대편 트리 파이버를 가리킴
이렇게 생성된 파이버는 state가 변경되거나 생명주기 메서드가 실행되거나 DOM의 변경사항이 필요한 시점 등에 실행됨.
리액트 파이버를 처리할 때마다 이러한 작업을 직접 바로 처리하기도 하고 스케줄링하기도 한다.
이런 작업은 작은 다위로 나눠서 처리할 수도, 애니메이션과 같이 우선순위가 높은 작업은 가능한 빨리 처리하거나, 낮은 작업을 연기시키거나 더 유연하게 처리할 수 있음.

리액트 파이버 트리

파이버 트리는 리액트 내부에서 두 개가 존재함
현재 모습을 담은 파이버 트리와, 작업중인 상태를 나타내는 workInProgress 트리
리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해 workInProgress 트리를 현재 트리로 바꿈.
이런 작업을 더블 버퍼링이라고 함.
 
더블 버퍼링은 리액트에서 새롭게 나온 개념이 아니고 컴퓨터 그래픽 분야에서 사용하는 용어.
그래픽을 통해 화면에 표시되는 것을 그리기 위해 내부적인 처리를 거쳐야 하는데 이런 처리를 거치면 사용자에게 미처 다 그리지 못한 모습을 보이는 경우가 발생함. (한 번에 모든 작업을 마무리해 다 그릴 수 없기 때문)
이런 상황을 방지하기 위해 보이지 않는 곳에서 그다음으로 그려야할 그림을 미리 그린 다음, 이것이 완성되면 현재 상태를 새로운 그림으로 바꾸는 기법을 의미함.
 
리액트도 미처 다 그리지 못한 모습을 노출시키지 않기 위해 더블 버퍼링 기법을 쓰는데 이런 더블 버퍼링을 위해 트리가 두 개 존재하며, 이 더블 버퍼링을 커밋 단계에 수행됨.
현재 UI렌더링을 위해 존재하는 트리인 current를 기준으로 모든 작업이 시작됨.
여기에서 업데이트가 발생하면 파이버는 리액트에서 새로 받은 데이터로 새로운 workInProgress 트리를 빌드하기 시작함.
workInProgress 트리를 빌드하는 작업이 끝나면 다음 렌더링에 이 트리를 사용함.
그리고 workInProgress 트리가 최종적으로 UI에 렌더링되어 반영이 완료되면 current가 이 wokInprogress로 변경됨.

파이버의 작업 순서

  1. 리액트는 beginWork() 함수를 실행해 파이버 작업을 수행하고 더 이상 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작됨
  1. 1에서 작업이 끝나면 completeWork() 함수를 실행해 파이버 작업을 완료함
  1. 형제가 있다면 형제로 넘어감
  1. 2, 3작업이 끝나면 return으로 돌아가 자신의 작업이 완료 되었음을 알림

파이버와 가상 DOM

리액트 컴포넌트에 대한 정보를 1:1 로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이루어짐.
이런 비동기 작업과 달리, 실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야 하고, 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있는 가능성이 높으므로 이런 작업을 가상에서, 즉 메모리상에서 먼저 수행해 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것이 목표.
리액트 파이버는 브라우저가 아닌 환경에서도 사용할 수 있기 때문에 파이버와 가상 DOM은 동일한 개념이 아님.

정리

가상 DOM과 리액트의 핵심은 DOM을 빠르게 그리고 반영하는 목표가 아니라 값으로 UI를 표현하는 것.
화면에 표시되는 UI를 자바스크립트 문자열 배열 등과 마찬가지로 값으로 관리하고 이런 흐름을 효율적으로 관리하기 위한 메커니즘이 리액트의 핵심