본문 바로가기
Language & Library/React

React의 Virtual DOM(VDOM)과 Diffing 알고리즘

by 미네마네모 2020. 9. 9.

DOM 이란?

DOM은 The Document Object Model의 약어로 직역하자면 문서 객체 모델 입니다.

즉, DOM은 HTML을 객체로 표현한 것이며 외부(JavaScript)와 접속할 인터페이스 입니다.

html로 작성된 코드는 HTML 파서에 의해서 DOM이라는 객체가 모인 트리로 변환 됩니다. (DOM 트리)

Virtual DOM 이란?

React 공식 홈페이지에서 Virtual DOM은 아래와 같이 표한하고 있습니다.

"UI로 표현될 객체를 가상 메모리에 저장하고 라이브러리에 의해 실제 DOM으로 동기화 하는 개념"

여기서 UI로 표현될 객체는 DOM을 말하며 라이브러리는 VDOM을 랜더링해주는 ReactDOM같은 것을 말합니다.

* 참고 : 바로가기

virtual DOM의 이점을 설명하는 이미지

Virtual DOM의 이점

React에서 state나 props가 갱신되면 render() 함수가 호출되어 새로운 앨리먼트(VDOM) 트리를 반환 합니다.

이때 효과적으로 UI를 갱신하기 위해서 기존의 VDOM(Old virtual DOM)과
새로운 VDOM(Old virtual DOM)의 차이점을 찾아내어 변경된 부분만 새롭게 랜더링 합니다.

즉, Virtual DOM은 DOM이 변경될 때마다 전체 DOM을 Reflow 하는 것이 아니라
가상의 DOM을 이용하여 한번만 Reflow를 수행함으로 부하를 줄여 빠르게 그릴 수 있다.

React의 Diffing Algorithm

기존의 DOM 트리를 새로운 트리로 변환하기 위하여 최소한의 연산을 하기위해서 최신 알고리즘을 사용하여도 n개의 노드가 있을때 O(n^3)의 복잡도를 가집니다.

* 여기서 최신 알고리즘 : 눈문 링크, 분석전 입니다.;

React는 두가지 가정을 기반으로 O(n) 복잡도의 heuristic 알고리즘을 구현 하였습니다.

아래는 두가지 가정을 기반으로 알고리즘이 구현 되었습니다.
1. Two elements of differnt 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를 통해 자식 엘리먼트의 변경 여부를 표시할 수 있다.

아래는 위 가정을 기반으로 구현된 알고리즘 내용 입니다.

React 사용 시 아래 사항을 숙지하는 것이 좋습니다. 😊

1. Element의 타입이 다른 경우

  • Virtual DOM Tree의 Root 엘리먼트(old vs new)의 타입이 다르면 DOM Tree를 버리고 새로운 트리를 구축합니다.
  • 엘리먼트가 제거되면 해당 엘리먼트와 하위 모든 엘리먼트의 state도 사라짐.
  • componentWillUnmount() : DOM 파괴될 때 실행.
  • componentWillMount() : DOM 삽입되기 전에 실행.
  • componentDidMount() : DOM 삽입된 후에 실행.
<!-- div와 span은 다르기 때문에 div는 제거된 후 span과 그 하위 엘리먼트가 추가됨 -->
<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

2. DOM의 Element의 타입이 같은 경우

 

  • 두 React DOM 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'}} />

3. 같은 타입의 Component Element

  • 컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됨.
  • 새로운 내용 반영을 위해서 props를 갱신.
  • componentWillReceiveProps() : props 갱신 전 호출
  • componentWillUpdate() : 컴포넌트가 업데이트되기 전 호출
  • render() 메소드 호출 후 이전과 비교하여 재귀적으로 처리

4. 반복적인 Element 처리

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

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


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

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

 

5. Keys

  • 4번에서 발생한 문제를 해결하기 위해서 key 속성을 지원함
  • React는 key 속성을 통하여 두 트리(old vs new)를 비교하여 일치하는지 확인
  • 차이점이 발생하면 추가함
  • 일반적으로 React에서 반복문을 통하여 Element를 생성할때 key 속성을 요구한다.
  • 하지만 위의 Case 외에 반복적으로 Element를 사용하고 상태에 따라서 Element가 추가/삭제될 수 있는 경우 key 속성을 사용하는 것이 성능상으로 좋다.
  • 반복되는 Element의 key 속성에 index를 사용을 권장하지 않는 이유는 반복되는 Element의 항목들이 재배열 되는 경우 비효율적으로 동작하기 때문이다. (재배열되면 index가 바뀌고 그에 따라 key도 변할 수 있기 때문, 문제 발생 예제)
<!-- 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>

6. 고려 사항

  • 상태에 따라 두 컴포넌트가 교체 될때, 두 컴포넌트의 결과물이 비슷한 경우 하나의 컴포넌트로 만들 것을 권장
  • key는 변하지 않고, 예상 가능하며, 유일해야함. (5번 참고)

Reference

* 아래 포스팅은 Virtual DOM를 Vanilla JS로 간단하게 만든 포스팅입니다.

 

[JavaScript] Vanilla JS로 React 만들기

시작하기 전에... Vanilla JS로 Virtual DOM을 랜더링 하는 간단한 유사 React를 만들어보려고 한다. 우아한 테크러닝 3기 (TypeScript & React)에서 김민태님의 강좌를 참고하였습니다. 글을 보기 전에 Virtual D

minemanemo.tistory.com

* 아래 공식 문서를 참고 하였습니다.

 

Reconciliation – React

A JavaScript library for building user interfaces

reactjs.org

 

댓글