리액트에서는 virtual DOM(가상 돔)이라는 것을 사용한다.
그렇다면 그냥 DOM을 사용하지 않고 왜 따로 가상 돔을 만들어서 사용하는 것인것일까?
그리고 그렇게 가상돔을 사용하면 어떠한 장점들이 있을까?
DOM(Document Object Model)과 Virtual DOM(Virtual Document Object Model)은 둘 다 웹페이지의 구조를 나타내는 개념입니다.
그러나 이 둘은 큰 차이점이 있습니다.
DOM은 웹페이지의 HTML 문서를 파싱해서 문서의 객체 모델을 생성합니다.
즉, HTML 문서의 요소(element), 속성(attribute), 텍스트(text) 등을 노드(node) 객체로 만들어서 웹페이지에 동적인 변화를 주는 역할을 합니다. JavaScript에서 DOM은 document 객체로 제공되며, 이를 통해 HTML 요소들을 선택, 생성, 수정, 삭제할 수 있습니다.
반면, Virtual DOM은 브라우저 내부에서 사용되는 개념으로, DOM의 가벼운 복사본이라고 볼 수 있습니다. 이를 사용하는 이유는, 웹페이지의 렌더링 성능 향상을 위해서입니다. 예를 들어, React와 같은 라이브러리에서는 먼저 데이터를 가지고 Virtual DOM을 생성하고, 그 후에 변경된 부분만 실제 DOM에 적용합니다. 이렇게 변경된 부분만 업데이트함으로써 DOM 조작이 최소화되어 렌더링 성능이 향상됩니다.
즉, DOM은 HTML 문서를 파싱해서 문서의 객체 모델을 생성하는 역할을 하고, Virtual DOM은 실제 DOM의 가벼운 복사본으로서 렌더링 성능을 최적화하는 역할을 합니다.
DOM을 변경한다고 해서 항상 웹 페이지 전체가 리렌더링되는 것은 아닙니다.
DOM은 웹 페이지의 구조를 나타내는 모델이기 때문에, 해당하는 노드의 변경이 있을 때, 해당 노드를 포함한 부분만 다시 그려지게 됩니다.
예를 들어, 특정 요소의 스타일을 변경하면, 그 요소와 그 자식 요소들만 다시 그려지게 됩니다. 또한, 텍스트 노드의 변경도 마찬가지로 해당하는 텍스트 노드만 다시 그려지게 됩니다.
하지만, DOM을 변경하는 작업이 빈번하게 일어나거나, 복잡한 DOM 구조를 가진 웹 페이지에서는 리렌더링이 빈번하게 일어나서 성능 이슈가 발생할 수 있습니다. 이런 경우에는 Virtual DOM을 사용하거나, 최적화된 DOM 조작 방법을 사용하는 것이 좋습니다.
DOM과 Virtual DOM의 가장 큰 차이점은 업데이트 방식에 있습니다.
DOM은 해당하는 노드의 변경이 있을 때, 해당 노드를 포함한 부분만 다시 그려지게 됩니다. 그러나 이런 작업이 빈번하게 일어나거나, 복잡한 DOM 구조를 가진 웹 페이지에서는 리렌더링이 빈번하게 일어나서 성능 이슈가 발생할 수 있습니다.
반면, Virtual DOM은 React와 같은 라이브러리에서 사용되는 개념으로, 변경된 부분만 실제 DOM에 적용함으로써 DOM 조작이 최소화되어 렌더링 성능이 향상됩니다. 이를 사용하는 이유는, 웹페이지의 렌더링 성능 향상을 위해서입니다.
따라서 DOM도 변경되는 부분에서만 리렌더링해주면, Virtual DOM과의 차이는 줄어들지만, 여전히 다른 차이점들이 존재합니다. Virtual DOM은 실제 DOM의 가벼운 복사본으로서 렌더링 성능을 최적화하는 역할을 하기 때문에, DOM보다 가볍고 빠른 처리가 가능합니다. 또한, DOM 조작이 최소화되기 때문에, 안정적인 사용자 경험을 제공할 수 있습니다.
Virtual DOM은 실제 DOM의 가벼운 복사본으로서, 메모리 상에서 처리가 가능합니다. 즉, DOM의 변경사항이 일어날 때마다, Virtual DOM에 해당하는 데이터를 업데이트하고, 이전 버전의 Virtual DOM과 새 버전의 Virtual DOM을 비교하여 변경된 부분만 실제 DOM에 업데이트하는 방식으로 작동합니다.
실제 DOM을 조작하는 것보다 Virtual DOM을 조작하는 것이 훨씬 빠르기 때문에, 리액트와 같은 라이브러리에서 Virtual DOM을 사용하여 성능을 향상시키고 있습니다.
또한, Virtual DOM은 필요한 경우에만 업데이트되기 때문에, 최적화된 렌더링을 제공할 수 있습니다.
이렇게 Virtual DOM을 사용함으로써, 성능이나 사용자 경험을 향상시키는 것이 가능합니다.
// 가상 돔 노드 생성
function createVNode(tag, props, children) {
return {
tag,
props,
children
};
}
// 실제 돔 노드 생성
function createNode(vNode) {
const el = document.createElement(vNode.tag);
for (const propName in vNode.props) {
el.setAttribute(propName, vNode.props[propName]);
}
if (vNode.children) {
vNode.children.forEach(child => {
el.appendChild(createNode(child));
});
}
return el;
}
// 이전 가상 돔과 새 가상 돔 비교
function diff(prevVNode, nextVNode) {
if (prevVNode.tag !== nextVNode.tag) {
return true;
}
if (JSON.stringify(prevVNode.props) !== JSON.stringify(nextVNode.props)) {
return true;
}
if (prevVNode.children && nextVNode.children) {
if (prevVNode.children.length !== nextVNode.children.length) {
return true;
}
for (let i = 0; i < prevVNode.children.length; i++) {
const prevChild = prevVNode.children[i];
const nextChild = nextVNode.children[i];
if (diff(prevChild, nextChild)) {
return true;
}
}
} else if (prevVNode.children || nextVNode.children) {
return true;
}
return false;
}
// 가상 돔 업데이트
function update(parent, prevVNode, nextVNode) {
if (!prevVNode) {
parent.appendChild(createNode(nextVNode));
} else if (!nextVNode) {
parent.removeChild(parent.firstChild);
} else if (diff(prevVNode, nextVNode)) {
parent.replaceChild(createNode(nextVNode), parent.firstChild);
} else {
if (prevVNode.children && nextVNode.children) {
for (let i = 0; i < prevVNode.children.length; i++) {
update(parent.firstChild, prevVNode.children[i], nextVNode.children[i]);
}
}
}
}
// 예제
const prevVNode = createVNode("div", { id: "app" }, [
createVNode("p", {}, ["Hello, World!"])
]);
const nextVNode = createVNode("div", { id: "app" }, [
createVNode("p", {}, ["Hello, Virtual DOM!"])
]);
const parent = document.getElementById("root");
update(parent, prevVNode, nextVNode);