React에는 Virtual DOM이라고 하는 가상의 DOM 객체가 있다.
이는 실제 DOM의 사본 같은 개념으로, React는 실제 DOM 객체에 접근하여 조작하는 대신 이 가상의 DOM 객체에 접근하여 변화 전과 변화 후를 비교하고 바뀐 부분을 적용한다. Virtual DOM이 무엇인지 알아보기 전에 Virtual DOM이 어떻게 탄생했는지 알아보자.
Virtual DOM은 Real DOM의 가벼운 사본과 같다. DOM을 Real DOM으로 부르는 이유는 Virtual DOM과 구분하기 위해서라고 생각하면 된다.
Virtual DOM이 나오게 된 배경을 보기 전에, DOM의 개념을 다시 정리해 보도록 하자. DOM은 Document Object Model의 약자로, 뜻을 그대로 풀자면 문서 객체 모델을 의미한다.
여기서 문서 객체란 브라우저가 JavaScript와 같은 스크립팅 언어가 <html>
, <head>
, <body>
와 같은 태그들에 접근하고 조작할 수 있도록 태그들을 트리 구조로 객체화 시킨 것을 의미한다.
다시 말하자면 DOM은 브라우저가 트리 구조로 만든 객체 모델이다.
[그림] DOM 객체는 트리 구조로 되어 있습니다.
이렇게 트리 구조로 DOM 객체가 이뤄져 있기 때문에 JavaScript는 쉽게 DOM 객체에 접근할 수 있고, DOM 객체를 조작할 수 있게 된다.
이렇게 프로그래밍 언어로 조작하는 DOM은 애플리케이션의 UI 상태가 변경될 때마다 해당 변경 사항을 나타내기 위해 업데이트가 된다.
만약 이런 DOM을 조작하는 정도가 잦다면 성능에 영향을 미치게 될 것이고, DOM의 렌더링은 브라우저의 파워, 즉 브라우저의 구동 능력에 의존하기 때문에 DOM의 조작 속도는 느려지게 된다.
DOM의 구조는 앞서 설명했듯이 계층적 구조로 되어 있는 트리이다.
자료구조 중에서 특히 트리는 “데이터 저장"의 의미보다는 “저장된 데이터를 더 효과적으로 탐색”하기 위해 사용되므로, 빠른 자료 탐색 성능이 장점인 자료구조라고 볼 수 있다.
그렇기 때문에 그런 트리 구조로 된 DOM은 JavaScript와 같은 스크립팅 언어가 접근하고 탐색하는 속도가 빠르기 때문에 변경 및 업데이트 속도 또한 빠르다.
[그림] 트리구조로 된 DOM은 JavaScript 같은 스크립팅 언어가 접근하고 탐색하는 속도가 빠릅니다.
그러나 DOM이 변경되고 업데이트가 된다는 것은 결국 브라우저의 렌더링 엔진 또한 리플로우(Reflow)한다는 것을 의미한다.
즉 업데이트 된 요소와 그에 해당하는 자식 요소들에 의해 DOM 트리를 재구축함으로써 재랜더링 과정을 거쳐 UI를 업데이트 해야 한다.
브라우저의 리플로우와 리페인트 과정은 다시금 레이아웃 및 페인트에 해당하는 재연산을 해야 하기 때문에 속도가 그만큼 느려지게 된다.
[그림] DOM의 변경 및 업데이트는 브라우저 또한 화면을 리플로우(Reflow)한다는 것을 의미합니다.
따라서 JavaScript로 조작하는 DOM의 요소가 많을수록 모든 DOM 업데이트에 대하여 리플로우를 해야 하므로 DOM의 업데이트에 대한 비용이 많이 들게 된다.
왜일까? DOM 조작은 모던 웹의 인터랙티브 요소에 빠질 수 없는 것이지만, 대부분의 JavaScript 프레임워크는 필요 이상으로 DOM을 업데이트 시킨다.
[그림] JavaScript의 DOM 조작 예시
여기 예를 들어 지금 6개의 컨텐츠가 있는데 그 컨텐츠 중 단 1개의 컨텐츠만 색상이 바뀌는 동작을 한다고 가정해보자.
나머지 5개의 컨텐츠는 내버려두고 그 1개의 컨텐츠만 색상을 바꿔 그리는 것이 가장 효율적이라는 걸 알지만, 대부분의 JavaScript 프레임워크는 그 1개의 컨텐츠만 색상을 바꿔 그릴 뿐 아니라 나머지 컨텐츠도 다시 그린다.
[그림] JavaScript 프레임워크는 나머지 DOM 요소까지도 업데이트를 시킨다.
이 예시는 브라우저의 성능에 크게 문제를 끼치지 않겠지만, 모던 웹은 이 외에도 엄청난 양의 DOM을 조작할 가능성이 있다.
계속해서 이런 비효율적인 업데이트를 반복한다면 극단적인 예로 프레임 드랍(frame drop)과 같은 치명적인 UX 문제가 발생할 수 있다.
결국 “바뀐 부분만 비교해서 그 부분만 렌더링을 할 수는 없을까?“ 라는 아이디어를 기반으로 React는 Virtual DOM을 세상에 내놓게 된 것이다.
그렇기 때문에 React에는 모든 DOM 객체에 대응하는 가상의 DOM 객체가 있다.
상대적으로 무거운 DOM에 비하여 React의 가상 DOM 객체는 실제 DOM 객체와 동일한 속성을 가지고 있음에도 “훨씬 가벼운 사본”이라고 표현할 수 있다.
다만 가상 DOM 객체는 화면에 표시되는 내용을 실제 DOM 객체처럼 직접 변경하는 것은 아니다.
가상 DOM은 가상의 UI 요소를 메모리에 유지시키고, 그 유지시킨 가상의 UI 요소를 ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기화시킨다.
실제 DOM을 조작하는 것은 실제로 브라우저 화면에 그리기 때문에 느리지만, 가상 DOM을 조작하는 것은 실제 DOM처럼 실제로 브라우저 화면에 그리는 것이 아니기 때문에 훨씬 속도가 빠르다.
[그림] 실제 DOM을 조작하는 것과 가상 DOM을 조작하는 것의 차이
가상 DOM을 조작하는 것과 실제 DOM을 조작하는 것의 차이가 잘 이해가 안 간다면, 집을 이사할 때를 떠올려 보자.
실제로 집을 이사하고 짐을 옮기는 과정(실제 DOM을 조작하는 것)은 힘이 많이 들고 복잡한 일이지만, 이사하기 전에 머릿속으로 이사하고 짐을 옮기는 장면을 그리는 것(가상 DOM을 조작하는 것)은 그보다 더 쉬운 일이다.
React는 새로운 요소가 UI에 추가되면 트리 구조로 표현이 되는 가상의 DOM이 만들어진다.
이러한 요소의 상태가 변경이 되면 다시 새로운 가상의 DOM 트리가 만들어진다.
그리고 이전의 가상의 DOM과 이후의 가상의 DOM의 차이를 비교한다.
이 작업이 완료가 되면 가상의 DOM은 실제 DOM에 변경을 수행할 수 있는 최상의 방법을 계산하기 시작한다.
이렇게 하면 실제 DOM은 최소한의 작업만 수행해도 렌더링을 할 수 있게 된다.
따라서 실제 DOM의 업데이트 비용을 줄일 수 있게 된다.
업데이트 비용을 줄일 수 있다는 것은 브라우저의 파워를 덜 쓴다는 의미이므로, 더 빠른 렌더링이 가능해진다.
아래 그림을 보면서 가상 DOM 트리가 실제 DOM 트리에 적용되는 과정을 보도록 하자.
[그림] 가상 DOM이 실제 DOM에 적용되는 과정을 약식으로 표현하자면 이렇습니다.
노란색 원들은 업데이트된 노드, 즉 UI 요소를 의미한다. React에서는 “상태가 변경된” UI 요소로 볼 수 있다.
그런 다음 가상의 DOM 트리의 이전 버전과 현재 가상 DOM 트리 간의 차이가 계산된다.
그런 다음 트리가 업데이트된 UI를 제공하기 위해 부분적으로 리렌더링 된다.
이렇게 업데이트된 트리는 실제 DOM으로 한꺼번에 업데이트가 된다.
가상 DOM은 추상화된 자바스크립트 객체의 형태를 가지고 있다.
DOM 트리 하나를 예시로 들어 보자.
이 DOM 트리는 JavaScript 객체로도 표현할 수 있다.
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이라고 생각해 보자.
실제 DOM과 마찬가지로 가상 DOM 또한 HTML 문서 객체를 기반으로 한다.
또한 추상화만 되었을 뿐 평범한 자바스크립트 객체이므로 실제 DOM을 건드리지 않고도 필요한 만큼 자유롭게 조작할 수 있다.