React _ Virtual DOM

이승미·2022년 7월 27일
0

Real DOM과 Virtual DOM

Real DOM (기존의 DOM)

  • Real DOM (Virtual DOM과 비교하기 위해 이렇게 부른다) 이란, 브라우저가 트리 구조로 만든 객체 모델 (트리구조이기 때문에 JS는 쉽게 DOM 객체 접근, 조작 가능)
  • DOM이 변경되고 업데이트 될 때마다 브라우저 역시 재렌더링 => 속도가 그만큼 느려지게 됨.
  • 여러 개의 요소 중 한 개만 변경할 때도 전체 DOM 요소를 다 재렌더링함 => 프레임 드랍같은 치명적 UX 문제 발생 가능
  • 바뀐 부분만 비교해서 그 부분만 렌더링 할 수 없을까?? => Virtual DOM

Virtual DOM (가상의 DOM)

  • React에는 모든 DOM 객체에 대응하는 가상의 DOM 객체가 존재, 실제 DOM 객체와 동일한 속성 가짐에도 훨씬 가벼움 (실제 DOM 객체처럼 변경은 불가능)
  • 가상의 UI 요소를 메모리에 유지시키고, 그 유지시킨 가상의 UI 요소를 ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기화시킴 => 속도 훨씬 빠름
  • 예시) ReaL DOM 조작 : 실제로 집을 이사하고 짐을 옮기는 과정
    Virtual DOM 조작 : 이사하기 전 머릿속으로 이사하고 짐 옮기는 장면을 그리는 과정

Virtual DOM Tree가 Real DOM Tree에 적용되는 과정
1. 새로운 요소가 UI에 추가되면 트리 구조로 표현이 되는 Virtual DOM이 만들어짐
2. 요소의 상태가 변경되면 다시 새로운 Virtual DOM Tree가 만들어짐
3. 이전의 Virtual DOM과 이후의 Virtual DOM의 차이를 비교함
4. 비교 후, Virtual DOM은 Real DOM에 변경을 수행할 수 있는 최상의 방법을 계산

= > Real DOM의 업데이트 비용 줄이고, 더 빠른 렌더링 가능


React Diffing Algorithm

React Diffing Algorithm?

기존의 Virtual DOM과 새 Virtual DOM을 비교할 때, React는 변경된 새 Virtual DOM Tree에 부합하도록 기존의 UI를 효율적으로 갱신하는 방법을 알아내야 함
=> 두 가지의 가정을 가진 시간복잡도 O(n)의 휴리스틱 알고리즘 구현

두 가지 가정

  • 각기 서로 다른 두 요소는 다른 트리를 구축할 것이다.
  • 개발자가 제공하는 key 프로퍼티를 가지고, 여러 번 렌더링을 거쳐도 변경되지 말아야 하는 자식 요소가 무엇인지 알아낼 수 있을 것이다.

React가 DOM Tree를 탐색하는 방법

기본적으로, React는 기존의 Virtual DOM Tree와 새 Virtual DOM Ttree를 비교할 때, 트리의 레벨 순서대로, 같은 레벨 위치끼리의 노드를 비교하여 탐색한다. (1 level 탐색 -> 2level 탐색....)

다른 타입의 DOM 엘리먼트인 경우

  • DOM Tree는 각 HTML 태그마다 규칙이 있어, 그 아래 들어가는 자식 태그가 한정적이다. (ex) ul -> li (O) , p -> p (X))
    즉, 자식 태그의 부모 태그가 정해져 있어 부모 태그가 달라지면 React는 기존의 Tree를 버리고 새 Tree를 구축한다.
<div>
  <About />  
</div>

// 부모 태그 div 에서 span으로 변경

<span>
  <About />
</span>
  • 이렇게 부모 노드가 바뀌어버리면, 이전 태그(div) 속 자식 노드인 컴포넌트 About은 완전히 파괴되고, 새로운 태그(span) 속 컴포넌트 About이 다시 실행된다.

    즉, 기존의 div 태그 속 컴포넌트 About의 state가 완전히 파괴된다.

같은 타입의 DOM 엘리먼트인 경우

  • 타입이 바뀌지 않을 경우, React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트한다 (React가 Real DOM이 아닌 Virtual DOM을 조작하기 떄문에!)

    즉, 업데이트 할 내용이 생기면 Virual DOM의 내부 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그 후 단 한번 Real DOM으로 렌더링을 시도한다.

    <div style={{color: 'red', fontWeight: 'bold'}} title='stuff' />
    // color를 red에서 blue로 변경 (타입은 변경되지 않고 있다)
    <div style={{color: 'blue', fontWeight: 'bold'}} title='stuff'/>
  • React는 두 엘리먼트를 비교하고, color 스타일만 수정되었다는 것을 알게 된다. 그래서 React는 color 스타일만 수정하고, fontWeight와 같은 다른 요소는 수정하지 않는다.

  • 이렇게 하나의 DOM 노드를 처리하고 난 후에 React는 해당 노드의 밑 자식 노드들 또한 순차적으로 동시에 순회하면서 차이가 발견될 때마다 처리한다. 이를 재귀적으로 처리 한다고 표현한다.

키 (key)

  • 효율적으로 Virual DOM을 조작할 수 있도록 도와주는 속성
<ul>
  <li key='2015'>Iron Man</li>
  <li key='2015'>Captain America</li>
</ul>

// key가 2014인 자식 엘리먼트를 처음에 추가한다.
<ul>
  <li key='2014'>Spider Man</li>
  <li key='2015'>Iron Man</li>
  <li key='2016'>Captain America</li>
</ul>
  • 만일, key 속성이 없을 때 기존 리스트 처음에 엘리먼트 Spider Man을 추가할 경우, React는 순차적으로 Iron Man과 Spider Man을 비교하고 리스트 전체가 바뀌었다고 받아들이게 된다. 그래서 전부 다 새롭게 렌더링하게 되는데, 이는 매우 비효율적인 방식이다.

  • 위의 코드와 같이 key 속성을 사용할 경우, React는 2014 라는 자식 엘리먼트가 새롭게 생겼고 2015, 2016 키를 가진 엘리먼트는 그저 위치만 이동했다는 것을 알게 된다. 그래서 다른 자식 엘리먼트는 변경하지 않은 채로, 추가된 엘리먼트만 변경한다.

  • 보통, key 속성에는 데이터베이스 상의 유니크한 값(id와 같은!) 을 부여해준다. (전역적으로 유일할 필요는 없고, 형제들 사이에서만 유일하면 됨)
    그런 값이 없다면 배열의 인덱스를 key 값으로 사용할 수 있다(그러나 이 경우는 최대한 피하는 것이 좋음)

  • key 값으로 유니크한 값을 가지고 싶을 때 npm으로 shortId나, nanoId를 깔아줄 수 있다.
    이 경우, 렌더링될 때마다 key값이 고정되는 것이 아니라 바뀌게 되므로 이 것을 사용할 때는 고정된 값에 부여해주는 것이 좋다.

...
const [person, setPerson] = useState([
  {id:shortid.generate(), name: tony stark},
  {id:shortid.generate(), name: peter parker},
  {id:shortid.generate(), name: steve rogers}
])

0개의 댓글