[React]Virtual DOM과 Diffing Algorithm

도시·2022년 9월 7일
2

React

목록 보기
9/11
post-thumbnail
post-custom-banner

1. Real DOM과 Virtual DOM

✍ Real DOM 이란?

Real DOM은 우리가 알고 있는 DOM과 같은 의미이며, Virtual DOM과 구분하기 위해 DOM을 Real DOM이라도 부른다.

  • DOM은 Document Object Model의 약자로, 브라우저가 트리 구조로 만든 문서 객체 모델이다.

    • 여기서 문서 객체란, 브라우저에서 <html>, <head>, <body> 태그에 접근하거나 조작할 수 있도록 태그들을 트리 구조로 객체화 시킨 것이다.

  • 위와 같이 DOM 객체는 트리구조로 되어 있기 때문에 JS가 쉽게 DOM 객체에 접근하거나 조작할 수 있다.

  • 자료구조에서 트리는 저장된 데이터를 더 효과적으로 탐색하기 위해 사용된다. 때문에 DOM은 JS가 쉽게 DOM 객체에 접근&탐색하는 속도가 빠르고, 업데이트 속도도 빠르다.

  • DOM의 조작 속도가 느려지는 이유:
    DOM이 변경되고 업데이트가 되면 브라우저 렌더링 엔진 또한 변경 사항을 나타내기 위해 Reflow, Repaint가 된다. 업데이트된 요소와 그에 해당하는 자식 요소들이 DOM 트리로 재구축된 후 리렌더링 과정을 거쳐 업데이트가 되면, 브라우저는 다시 레이아웃 및 페인트에 대한 재연산을 해야되기 때문에 속도가 느려진다. 즉, DOM의 렌더링은 브라우저의 구동 능력에 의존하기 때문에 DOM의 조작 속도는 느려지게 되는 것이다. DOM을 조작하는 정도가 잦다면 성능에 영향을 미칠 수 있다.


✍ Virtual DOM이란?

DOM을 추상화한 가상의 객체로, Virtual DOM은 Real DOM 객체와 동일한 속성을 가지고 있는 가벼운 사본이다.

  • React에는 모든 DOM 객체에 대응하는 가상의 DOM 객체가 있다.
  • React는 실제 DOM 객체에 접근하여 조작하는 대신 이 가상의 DOM 객체에 접근하여 변화 전과 변화 후를 비교하고 바뀐 부분만 렌더링한다.
  • Virtual DOM은 가상의 UI 요소를 메모리에 유지시키고, 그 유지시킨 가상의 UI 요소를 ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기화시킨다.(Virtual DOM 객체는 화면에 표시되는 내용을 Real DOM 객체처럼 직접 변경하지 않음)
  • Real DOM은 실제 브라우저 화면을 그리기 때문에 조작 속도가 느리지만, Virtual DOM은 실제로 브라우저 화면에 그리는 것이 아니기 때문에 조작 속도가 훨씬 빠르다.
  • Virtual DOM의 형태:
    • 실제 DOM과 마찬가지로 가상 DOM 또한 HTML 문서 객체를 기반으로 한다.
    • 아래와 같이 Virtual DOM은 추상화된 자바스크립트 객체의 형태를 가지고 있다.
const vDom = {
	tagName: "html",
	children: [
		{ tagName: "head" },
		{ tagName: "body",
			children: [
				tagName: "ul",
				attributes: { "class": "list"},
				children: [
					{
						tagName: "li",
						attributes: { "class": "list_item" },
						textContent: "List item"
					}
				]
			]
		}
	]
}

⚠ 한계는 존재한다

  • React, Vue 등을 이용해서 Virtual DOM을 쓴다고 무조건 빠른 건 아니다.
  • Virtual DOM은 메모리에 존재한다. 때문에 메모리의 사용이 많이 늘어날 수 있다.
  • Virtual DOM을 통헤 컴포넌트를 많이 조작하게된다면 오버헤드가 생길 수 있다.
    • 오버헤드: 처리를 하기 위해 들어가는 간접적인 처리 시간 OR 메모리를 말한다.
  • 동시에 업데이트되는 것에 한해서만 렌더링이 된다.


2. Diffing Algorithm

✍ React의 Diffing Algorithm이란?

Diffing Algorithm, 비교 알고리즘이란, React에서 두 개의 트리를 비교할 때 두 엘리먼트의 루트 엘리먼트부터 비교한 후, 루트 엘리먼트 타입에 따라 트리를 구축하는 방식의 알고리즘이다.

  • 엘리먼트 타입이 다른 경우:
    • 이전 트리를 버리고 완전히 새로운 트리를 구축한다.
    • 제거된 엘리먼트의 하위 엘리먼트 state도 사라진다. 부모가 사라지면 자식들도 함께 사라진다는 뜻.
<!-- 타입이 다르기 때문에 div는 제거되고 span과 하위 요소인 Counter를 사용 -->
<div>
  <Counter />
</div>
<span>
  <Counter />
</span>
  • DOM의 엘리먼트 타입이 같고 속성이 다른 경우:
    • 두 엘리먼트 속성을 확인하여 동일한 내역은 유지하고 변경된 속성만 갱신한다.
<!-- 변경된 속성은 className이므로, className만 갱신 -->
<div className="before" title="stuff" />
<div className="after" title="stuff" />
<!-- style 속성도 변경사항인 color만 갱신 -->
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
  • 자식 요소가 바뀌는 경우:
    • DOM 노드의 처리가 끝나면, 해당 노드의 자식들을 재귀적으로 처리한다.
    • 첫번째 자식부터 비교하며 변경하기 때문에, 첫 요소가 변경되면 처음부터 비교를 해야하는 문제를 가지며, 이는 성능저하를 야기한다. 이 문제는 key porp을 통해 해결할 수 있다. key에 관한 건 아래에 자세히 메모.
<!-- 이전 트리 -->
<ul>
  <li>first</li>
  <li>second</li>
</ul>
<!-- 새로운 트리 -->
<!-- first, second 요소가 같지만, 첫 번째부터 새로운 요소가 추가 되었기 때문에 첫 번째부터 트리를 만든다. -->
<ul>
  <li>third</li> // 첫 번째에 새로운 요소 추가
  <li>first</li>
  <li>second</li>
</ul>

✍ Diffing Algorithm이 나오게 된 배경

기존의 DOM 트리를 새로운 트리로 변환하기 위하여 최소한의 연산을 하는 알고리즘을 사용한다. 이때 알아낸 조작 방식은 알고리즘 O(n^3) 의 복잡도를 가지고 있다. 만약,이 알고리즘을 React에 적용한다면, 1000개의 엘리먼트가 있다는 가정하에 실제 화면에 표시하기 위해 1000^3인 10억번의 비교 연산을 해야한다. 이는 너무 비싼 연산이기에 React는 두 가지 가정을 가지고 시간 복잡도 O(n)의 새로운 Heuristic Algorithm을 구현했다.


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 프로퍼티를 통해 자식 요소의 변경 여부를 표시할 수 있다.

=> 여러 번 렌더링을 거쳐도 변경되지 말아야 하는 자식 요소가 무엇인지 알아낼 수 있다.


위와 같은 가정을 통해 React는 비교 알고리즘을 사용한다.

✍ key에 관하여

위에서 말했다 싶이, 만약 자식 노드들이 key를 갖고 있다면, React는 그 key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 아닌지 확인할 수 있다.

<ul>
  <li key="2021">Duke</li>
  <li key="2022">Villanova</li>
</ul>

<!-- key가 2014인 자식 엘리먼트를 처음에 추가. -->
<ul>
  <li key="2020">Connecticut</li>
  <li key="2021">Duke</li>
  <li key="2022">Villanova</li>
</ul>

위 예제에서는 key 속성을 통해 ‘2020’라는 자식 엘리먼트가 새롭게 추가됐다.
React는 기존의 동작 방식대로 다른 자식 엘리먼트는 변경하지 않고, key 속성을 통해 해당 key가 존재하는지 확인한 후 추가된 요소만을 변경한다.

이때 key는 유일한 값을 사용해야 한다. key 속성에는 보통 데이터 베이스 상의 유니크한 값(ex. Id)을 부여한다. key는 형제 엘리먼트 사이에서만 유일하면 된다.(전역에서 유일할 필요는 없음)

key가 유니크한 값이 아니라면, 배열의 인덱스를 key로 사용할 수 있다.
다만, 배열이 다르게 정렬될 경우가 생긴다면 배열의 인덱스를 key로 선택했을 경우는 비효율적으로 동작할 수 있다. 그 이유는 배열이 다르게 정렬된다 하더라도 인덱스는 그대로 유지되기 때문이다. 인덱스는 그대로지만 그 요소가 바뀌어버린다면 React는 배열의 전체가 바뀌었다 생각하고 기존의 DOM 트리를 버리고 새로운 DOM 트리를 구축해버린다.


참고자료

profile
UI·UX Designer/Frontend Dev
post-custom-banner

0개의 댓글