bkdragon's log

[React] Virtual DOM 본문

React

[React] Virtual DOM

bkdragon 2023. 6. 25. 10:34

DOM은 웹 페이지나 웹 앱에 있는 HTML 요소들을 트리구조로 표현한 객체 모델이다. 브라우저는 DOM 을 이용해 화면에 요소들을 렌더링하는데 DOM이 가진 문제점이 몇가지 있다.

 

노드수가 많아질 수록 속도가 느려지고, DOM 업데이트가 잦으면 오류를 발생시킬 수 있다.

React를 활용하여 만든 웹은 SPA를 사용한다. 모든 리소스가 들어가있는 하나의 HTML 문서를 지속적으로 재렌더링 해줘야하는 문제점도 생긴다.

 

JavaScript에서 getElementById(), getElementByClass(), querySelector() 등으로 돔에 접근하는데 이때 아래와 같은 과정이 일어난다.

 

  1. 브라우저가 HTML을 분석하여 원하는 노드를 찾는다.
  2. 찾은 요소의 자식 요소를 제거한다.
  3. 요소를 업데이트한다.
  4. 부모 및 자식 노드에 대한 CSS를 다시 계산한다. (Reflow가 발생할 수 있다.)
  5. 브라우저 디스플레이에 페인팅된다. (Repaint)

수정사항이 발생할 때마다 새롭게 렌더 트리(DOM + CSSOM, 화면에 그려지는 최종 트리) 만들어지게 되는 것이다.

 

Virtual DOM

VDOM은 실제 DOM의 가벼운 복사이다. VDOM은 실제 DOM을 직접 처리하지 않고 메모리에서 미리 처리하고 저장한 후 실제 DOM과 동기화 한다.

아래는 과정이다.

 

  1. 데이터가 업데이트 되면 새로운 VDOM을 생성한다. (VDOM은 한 개가 아니다. React는 업데이트된 VDOM과 이전 VDOM의 두 가지 VDOM을 유지한다. )
  2. 이전 VDOM에 있던 내용과 현재 VDOM의 내용을 비교(Diffing 알고리즘)한 뒤 효율적으로 업데이트하기 위해 필요한 최소한의 변경 사항을 결정한다,
  3. 실제 DOM에 적용한다.

 

Diffing

React에서 두 개의 VDOM을 비교할 때 사용하는 알고리즘으로 두 가지의 가정을 통해 복잡도를 O(n)으로 줄인 알고리즘이다. (트리 비교 알고리즘은 원래 O(n^3)이다.)

 

1. Two elements of different types will produce different trees.

: 서로 다른 타입을 가진 두 엘리먼트는 다른 트리를 만들어 낸다.

2. The developer can hit at which child elements may be stable across different renders with a key prop.

: 개발자가 key prop를 통해 자식 엘리먼트의 변경 여부를 표시할 수 있다.

 

Element의 타입이 다른 경우

  • Root 엘리먼트의 타입이 다르면 DOM Tree를 버리고 새로운 트리를 구축한다.
  • 엘리먼트가 제거되면 해당 엘리먼트와 하위 모든 엘리먼트의 state도 사라진다.
<!-- div와 span은 다르기 때문에 div는 제거된 후 span과 그 하위 엘리먼트가 추가됨 -->
<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

 

DOM의 Element의 타입이 같은 경우

  • Element의 타입이 같은 경우,Attribute(속성)을 확인하여 동일한 내역은 유지하고 변경된 속성만 갱신한다.
  • 해당 처리는 하위의 자식 노드들에게 재귀적으로 처리한다.
  • style 갱신도 변경 사항만 갱신 합니다.
<!-- Attribute 갱신 -->
<div className="before" title="stuff" />

<div className="after" title="stuff" />

<!-- Style 갱신 // 결과적으로 color만 갱신, fontWeight는 유지 -->
<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

 

같은 타입의 Component Element

  • 컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지된다.
  • 새로운 내용 반영을 위해서 props를 갱신한다.

 

반복적인 Element 처리

  • DOM 노드의 자식들을 반복적으로 처리할 때, 기본적으로 두 트리를 순차적으로 비교하고 차이점이 있으면 변경한다.
  • 순회 비교를 하기 때문에 Element 리스트의 앞에 추가하는 것 보다 뒤에 추가하는 것이 성능상으로 좋다.
<!-- 문제 X -->
<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

<!-- 성능상 문제 발생할 수 있음 -->
<ul>
  <li>2</li>
  <li>3</li>
</ul>

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

 

Keys

  • 4번에서 발생한 문제를 해결하기 위해서 key 속성을 지원한다.
  • React는 key 속성을 통하여 두 트리를 비교하여 일치하는지 확인한다,
  • 차이점이 발생하면 추가한다.
  • 일반적으로 React에서 반복문을 통하여 Element를 생성할때 key 속성을 요구한다.
  • 하지만 위의 Case 외에 반복적으로 Element를 사용하고 상태에 따라서 Element가 추가/삭제될 수 있는 경우 key 속성을 사용하는 것이 성능상으로 좋다.
  • 반복되는 Element의 key 속성에 index를 사용을 권장하지 않는 이유는 반복되는 Element의 항목들이 재배열 되는 경우 비효율적으로 동작하기 때문이다.
<!-- key 속성으로 4번의 성능 문제 해결 -->
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

 

Reference

https://minemanemo.tistory.com/120

https://legacy.reactjs.org/docs/reconciliation.html

'React' 카테고리의 다른 글

클로저와 useState  (0) 2023.07.10
[React] 성능 최적화  (0) 2023.06.29
[React Query] Mutation 이후 캐시 데이터 직접 변경하기  (0) 2023.06.24
[React-hook-form] register, handleSubmit 살짝 파헤치기  (0) 2023.06.12
커링과 HOC  (0) 2023.05.31