[TIL] - Virtual DOM이 빠른 이유와 리액트의 Diffing 알고리즘

sehannnnnnn·2022년 11월 25일
0

React Beginner

목록 보기
5/5
post-thumbnail

DOM

DOM 객체는 브라우저에서 HTML문서를 조작하기 위한 인터페이스이다. DOM 객체는 트리구조로 되어 있으며, Document 라는 최상위 객체 아래 html element들이 자식으로 이루어진 모습이다.

DOM은 조작 속도가 느리다.
일부를 바꿔야한다면 전부를 바꾼다. -DOM 객체-

DOM이 변경되고 업데이트 되면 브라우저의 렌더링 엔진 또한 리플로우(reflow) 한다. DOM 트리 내부에 요소 속 상태가 변화할때 브라우저는 DOM 트리를 재구축하기 위해 재렌더링과 UI업데이트가 이루어진다는 것이고, 이는 레이아웃 및 페인트에 해당하는 재연산을 해야하기 때문에 속도가 많이 느려지게 된다

Virtual DOM

React를 사용하는데에 장점은 가상 DOM 객체를 다루는 것이라고 어렴풋이 알고는 있었지만 이 가상 DOM 이라는 것이 어떻게 생긴 것인지 구조나 원리를 자세히 알아볼 필요가 있었다.

일단 React를 통해 생성되는 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"
					}
				]
			]
		}
	]
}

이러한 가상 DOM을 조작한다고 해서 브라우저의 렌더링이나 재연산을 수행하지 않기 때문에 훨씬 수월한 것은 당연하다. React는 이러한 가상 DOM의 UI요소들을 메모리에 유지시키고 있다가, ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기화 시킨다.

어떻게 더 빠른 것인가?

리액트에서는 DOM 상태가 업데이트가 되면 이전 가상 DOM 과 다른 새로운 가상 DOM을 만들어낸다.

이 두개의 트리를 차이를 비교하며 React는 가장 효율적으로 교체하는 방법을 계산해내고 적용하는데 이렇게 하면 실제 DOM은 최소한의 작업만으로 렌더링을 수행할 수 있게 된다.

이 과정을 Diffing 알고리즘이라고 한다.

Diffing 알고리즘

DOM 트리 노드를 가장 효율적으로 교체하는 방법은 어떻게 알 수 있는가? 리액트는 이를 처리하기 위해 두가지 가정을 하고 알고리즘을 돌리는데

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

react가 DOM 트리를 탐색하는 방법

기본적으로 같은 레벨끼리 비교하는 BFS(너비 우선탐색)을 선택하여 각 노드를 순차적으로 파악해 간다.

  • 다른 타입의 DOM 엘리멘트를 만났을 경우
    👉 수정된 가상 DOM 과 이전 원본 가상 DOM 에서 한 엘리멘트의 태그가 서로 다를 경우, 리액트는 이 노드를 기준으로 아래 자식을 수정된 DOM 객체에서 가져온다.
<div>
	<Counter />
</div>

//부모 태그가 div에서 span으로 바뀝니다.
<span>
	<Counter />
</span>
  • 같은 타입의 DOM 엘리멘트를 만났을 경우
    👉 둘의 타입은 같지만 내부 상태가 변경될 경우 리액트는 어떤 부분이 수정되어 달라졌는지를 알아채고 수정된 부분만 변경을 수행한다. 이후 오는 자식 node에 경우엔 순차적으로 순회하며 차이가 변경될마다 변경한다. (재귀적으로 처리한다.)
<div className="before" title="stuff" />

//기존의 엘리먼트가 태그는 바뀌지 않은 채 className만 바뀌었습니다.
<div className="after" title="stuff" />
  • 자식 엘리먼트의 재귀적 처리
    👉 차이점이 발생한 엘리먼트가 자식 엘리먼트들을 갖고 있을 때, react는 전체 자식 node를 순차적으로(위에서 아래로) 순회하며 변경점을 찾는다. 이 때 하나라도 (자식 요소의 추가, 수정) 다른점이 발견 된다면 이후 남아있는 자식요소는 전부 변경되었다 생각하고 수정된 가상 DOM 에 부분을 적용합니다.
<ul>
  <li>first</li>
  <li>second</li>
</ul>

//자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했습니다.
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>
  • 키 (key) 속성의 활용
    👉 그런데 위에 재귀적 처리를 할 시, 첫 요소만 수정된 경우 변경이 불필요한 이후 요소들을 변경해주어야 하는 비효율성이 있다. 이 경우 각 자식 요소들의 유일성을 보장 할 수 있는 key 속성을 이용하여, 기존의 트리의 자식과 새로운 트리의 자식이 일치하는지 아닌지 확인 할 수 있다.
    키값과 해당 엘리먼트가 갖고 있는 요소가 같다면, 불필요한 교체작업을 수행하지 않아도 된다.
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

//key가 2014인 자식 엘리먼트를 처음에 추가합니다.
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>
profile
FE 개발자 준비생 블로그 🪐

0개의 댓글