DOM | PoiemaWeb
DOM과 Virtual DOM
Virtual DOM 동작 원리와 이해 (with 브라우저의 렌더링 과정)
NAVER D2
Virtual DOM
DOM과 Virtual DOM
Document object model, 웹문서를 로드한후 파싱하여 웹문서를 브라우저가 이해할 수 있는 구조로 구성하여 메모리에 적재하는 것
DOM은 웹문서의 모든 요소와 요소의 어트리뷰트, 텍스트를 각각의 객체로만들고 이를 트리구조로 구성한 것을 의미한다.
DOM은 js를 통해 동적으로 변경할 수 있으며, 변경된 DOM은 렌더링에 반영된다.
DOM은 DOM API로 프로퍼티와 메소드를 갖는 자바스크립트 객체를 통해 DOM에 접근하고 수정할 수 있는 방법을 제공한다.
문서 노드 (Document)
요소 노드 (Element)
어트리뷰트 노드 (Attribute)
텍스트 노드 (Text)
<html>
<head>
</head>
<body>
<div class="dailyfunding-wrapper">
<p class="daily-intern">뚱이</p>
</div>
</body>
</html>
const dailyIntern = document.getElementByClassName("daily-intern")[0];
dailyIntern.textContent = "단풍이";
const Intern = document.getElementByClassName("dailyfunding-wrapper")[0];
const Salt = document.createElement("p");
Salt.classList.add("daily-intern");
Salt.textContent = "소금이";
Intern.appendChild(Salt);
이러한 작업은 프로젝트의 규모가 커지고, 코드가 복잡해지면 이러한 작업을 수행하기가 어려워진다.
getElementByClassName
메소드를 통해 특정 class 를 탐색하지만, 코드가 복잡해진다면 특정 class를 찾아 업데이트하는 쿼리비용은 매우 비싸진다.
이외에도 innerText
를 사용하여 문서의 큰 부분을 한번에 바꾸는 방법도 있다
const Intern = document.getElementByClassName("dailyfunding-wrapper")[0];
Intern.innerText = `
<p class="daily-intern">단풍이</p>
<p class="daily-intern">소금이</p>
`;
돔 조작은 웹페이지를 수정하는데 유용하지만 문제점도 존재한다.
<div>
태그의 색상을 업데이트한다고 가정헀을때 해당 DOM node 객체에 접근하여 색상 속성을 업데이트하면 된다. 이때는 트리의 나머지 노드에 영향을 끼치지않는다.<li></li>
) 가 추가된다고하면, 하나의 리스트마다 새 노드가 DOM에 추가되어, 트리가 매번 업데이트되는 문제점이 발생한다.위에 다룬것처럼 DOM은 DOM을 생성할때마다, Render Tree의 생성부터 layout, painting까지 의 작업을 다시 해야한다. DOM자체는 빠를지라도, 브라우저가 작업해야하는 작업이 많아지면 이는 성능 저하로 연결된다.
이에 대한 대안으로 React에서는 Virtual DOM을 사용한다.
Vitual DOM에 대해 들어가기 앞서 브라우저 동작 방식을 먼저 간단하게 살펴보자.
paint()
메소드가 호출되어 화면에 표시된다.
DOM을 생성할때마다 위의 과정들을 모두 다시해야한다.
이 일련의 과정들은 DOM을 생성할때마다 Render Tree부터 페인팅까지 모두 다시 해야하며 브라우저의 성능저하로 이어진다. 이는 아까 다뤘던 DOM의 문제점이 된다.
<html>
<head>
</head>
<body>
<div class="dailyfunding-wrapper">
<p class="daily-intern">뚱이</p>
</div>
</body>
</html>
const vdom = {
tagName: 'html',
childern : [
{ tagName: 'head' },
{
tagName: 'body',
children: [
{
tagName: 'div',
attributes: { 'class': 'dailyfunding-wrapper' },
children: [
{
tagName: 'p',
attributes: { 'class': 'daily-intern' },
textContent : '뚱이'
}
]
}
]
}
]
}
const intern_list = {
tagName: 'div',
attributes: { 'class': 'dailyfunding-wrapper' },
children: [
{
tagName: 'p',
attributes: { 'class': 'daily-intern' },
textContent: '뚱이'
}
]
}
const new_intern_list = {
tagName: 'div',
attributes: { 'class': 'dailyfunding-wrapper' },
children: [
{
tagName: 'p',
attributes: { 'class': 'daily-intern' },
textContent: '단풍이'
},
{
tagName: 'p',
attributes: { 'class': 'daily-intern' },
textContent: '소금이'
}
]
}
Virtual DOM 이 어떻게 변경 되는 부분만 알아낼수있을까에 대한 답
Reconciliation 과정을 통해 서로 다른 두 DOM Tree를 비교하여 하나의 실제로 반영될 DOM Tree를 만들어낸다.
Reconciliation 두가지 가정
부모노드의 타입이 다르면, 자식 노드는 비교하지 않는다.
엘리먼트 타입이 다른 경우
<div>
<Counter />
</div>
<span>
<Counter />
</span>
위의 노드가 아래로 바뀌면 자식 노드인 컴포넌트는 완전히 해제되고 state 또한 파괴된다. 부모 엘리먼트 타입이 다를 경우 처음부터 렌더하는것이 효율적이다. 이는 display: block
규칙이 무너지기 때문에 렌더링 결과물이 어차피 달라지게 되기 떄문이다.
<div className="before" title="세미나" />
<div className="after" title="세미나" />
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>
<ul>
<li key="a">first</li>
<li key="b">second</li>
</ul>
<ul>
<li key="c">zero</li> {/* 추가, 새로 렌더링 */}
<li key="a">first</li> {/* 이전의 key와 동일, 순서만 변경. */}
<li key="b">second</li> {/* 이전의 key와 동일, 순서만 변경. */}
</ul>
Reconciliation Process
Component render() — updating the Virtual DOM, running the diffing algorithm and finally updating the DOM
How Virtual-DOM and diffing works in React
Reconciliation에 대한 알고리즘 설명을 자세하게 다룬 포스팅