React를 사용하면서 작동방식에 대한 고민을 많이 하지 못했는데, 기본 개념중 하나인 Virtual DOM에 대해서 정리하고자 했습니다.
문서 객체 모델(The Document Object Model, 이하 DOM) 은 HTML, XML 문서의 프로그래밍 interface 입니다. DOM은 문서(HTML)의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는 역할을 합니다.
DOM은 원본 HTML 문서의 객체 기반 표현 방식입니다. 둘은 서로 비슷하지만, DOM이 갖고 있는 근본적인 차이는 단순 텍스트로 구성된 HTML 문서의 내용과 구조가 객체 모델로 변환되어 다양한 프로그램에서 사용될 수 있다는 점입니다.
DOM의 개체 구조는 “노드 트리”로 표현됩니다.
아래 HTML 예시에서,
아래와 같은 노드 트리로 표현 됩니다.
노드 트리를 자바스크립트 객체로도 표현 할 수 있습니다.
{
type: 'body',
props: {},
children: [
{
type: 'h1',
props: {},
children:['Hello, World!']
},
{
type: 'p',
props: {},
children:['How are you?']
}
]
}
DOM을 이해하기 위해 웹 브라우저의 Workflow에 대해 간략하게 설명하겠습니다.
웹 브라우저가 원본 HTML 문서를 읽어들인 후, 스타일을 입히고 대화형 페이지로 만들어 뷰 포트에 표시하기까지의 과정을 거칩니다.
node 가 변경 될 때 마다 위의 과정을 새롭게 해야 하기 때문에 속도가 느려지게 됩니다.
Virtual DOM은 어떻게 위의 문제를 해결할까요?
데이터가 업데이트 되면, 전체 UI를 Virtual DOM에 리렌더링 합니다.
이전 Virtual DOM에 있던 내용과 현재의 내용을 비교합니다.
바뀐 부분만 실제 DOM에 적용이 됩니다.
Virtual DOM이 렌더 횟수를 줄여주는 예시
<!DOCTYPE html>
<html>
<head> </head>
<body>
<div id="root">
<ul class="list">
<li>red apple</li>
<li>yellow banana</li>
<li>yellow mango</li>
</ul>
</div>
</body>
</html>
비효율적 업데이트 (DOM)
const root = document.querySelector('#root')
const liTags = root.querySelectorAll('li')
// 화면 렌더링 발생 1
liTags[0].textContent = 'red apple'
// 화면 렌더링 발생 2
liTags[1].textContent = 'yellow banana'
// 화면 렌더링 발생 3
liTags[2].textContent = 'yellow mango'
효율적인 업데이트 (Virtual DOM)
const root = document.querySelector('#root')
const virtualDOM = document.createElement('ul')
virtualDOM.classList.add('list')
const appleLI = document.createElement('li')
appleLI.textContent = 'red apple'
const bananaLI = document.createElement('li')
bananaLI.textContent = 'yellow banana'
const mangoLI = document.createElement('li')
mangoLI.textContent = 'yellow mango'
virtualDOM.appendChild(appleLI)
virtualDOM.appendChild(bananaLI)
virtualDOM.appendChild(mangoLI)
// 이 때 한번 뷰가 다시 렌더링 되어 진다.
root.replaceChild(virtualDOM, root.querySelector('ul'))
예시를 들기 위해 사용한 코드입니다. 위의 virtuadlDOM이란 변수가 실제 Virtual DOM을 뜻하지는 않습니다.
Virtual DOM는 어떻게 변경 되는 부분만을 알아 낼 수 있을까요? 그 답은 Reconciliation에 있습니다.
전체 DOM 트리를 탐색하고 비교하는 일반적인 알고리즘(diff algorithm)이 있지만, 이 알고리즘은 O(n^3)의 복잡도를 갖습니다.
예를 들어, 1000개의 엘리먼트를 그리기 위해 10억 번의 비교 연산을 수행해야 합니다. React는 대신, 두 가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했습니다.
Reconciliation 두 가지 가정
1. 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.(엘리먼트가 다르면 비교하지 않는다)
react는 DOM 트리를 level-by-level로 탐색합니다. 너비 우선 탐색의 일종이라 볼 수 있습니다.
<div>
<Counter />
</div>
⬇︎
<span>
<Counter />
</span>
위의
같은 타입의 두 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 예를 들어,
<div className="before" title="stuff" />
<div className="after" title="stuff" />
이 두 엘리먼트를 비교하면, React는 현재 DOM 노드 상에 className 만을 수정합니다.
자식 엘리먼트의 순서가 다른 경우는 아래와 같이 매우 비효율적으로 작업을 수행하게 된다.
<ul>
<li>first</li>
<li>second</li>
</ul>
⬇︎
<ul>
<li>zero</li> {/* first가 zero가 됐군. 새로 렌더링! */}
<li>first</li> {/* second가 first가 됐군. 새로 렌더링! */}
<li>second</li> {/* 추가됐네. 새로 렌더링! */}
</ul>
위 와 같은 비효율적인 문제는 key를 통해 해결 될 수 있다.
<ul>
<li key="a">first</li>
<li key="b">second</li>
</ul>
⬇︎
<ul>
<li key="c">zero</li> {/* 추가됐네. 새로 렌더링! */}
<li key="a">first</li> {/* 아까 그 녀석이군. 순서만 바꾸자. */}
<li key="b">second</li> {/* 아까 그 녀석이군. 순서만 바꾸자. */}
</ul>
아래의 그림은 Reconciliation Process를 보여줍니다.
Component render() — updating the Virtual DOM, running the diffing algorithm and finally updating the DOM
이렇듯 React는 Virtual DOM을 통해 렌더를 한 번으로 줄여 효율적으로 HTML을 제어하게 됩니다.
추가로 리액트를 사용하는것이 무조건적인 성능 향상이 아닙니다. 성능적인 측면도 중요하지만 프론트엔드 뷰 라이브러리를 사용했을 때의 장점은 데이터의 흐름과 구성을 일관되게 해준다는 것이 장점입니다. 리액트를 사용하기 전에는 데이터의 소스와 DOM의 조작에 대한 자유도가 높기 때문에 일관된 패턴의 코드를 짜기 어려웠습니다. 하지만 리액트를 사용함으로써 데이터의 흐름을 강제하고, 그리고 데이터의 변화에 따라 화면에 변화를 쉽게 확인 할 수 있게 되었습니다.
참고
DOM 특징
http://www.ktword.co.kr/abbr_view.php?nav=2&m_temp1=2506&id=465
DOM
https://wit.nts-corp.com/2019/02/14/5522
Virtual DOM
https://medium.com/nybles/demystifying-react-s-virtual-dom-eb0f2dc0717a
https://www.codecademy.com/articles/react-virtual-dom
reconciliation
https://ko.reactjs.org/docs/reconciliation.html
https://meetup.toast.com/posts/110
https://www.huskyhoochu.com/virtual-dom/
http://blog.drakejin.me/React-VirtualDOM-And-Repaint-Reflow/
좋은 포스팅 감사합니다