2025년 05월 02일
DOM은 문서 객체 모델(Document Object Model)의 약자로, 웹 브라우저가 HTML 문서를 인식하고 조작할 수 있도록 하는 표준 프로그래밍 인터페이스입니다.
즉, 웹 브라우저는 HTML 페이지를 읽어 들이고 나면, 구문을 분석해 노드와 객체의 트리, 다시 말해 DOM이라고 부르는 객체 모델로 변환합니다.
DOM은 웹 페이지의 현재 상태를 실시간으로 표현한 것이므로, 사용자가 페이지와 상호 작용하는 대로 계속 업데이트 됩니다.
아래 예시는 웹 브라우저가 어떻게 DOM을 생성하는지 보여주는 예시입니다.
<!DOCTYPE html>
<html>
<body>
<h1>Hello, world!</h1>
</body>
</html>
// 위 예시의 DOM 트리
const dom = {
type: 'document',
doctype: 'html',
children: [
{
type: 'html',
children: [
{
type: 'body',
children: [
{
type: 'h1',
children: [
{
type: 'text',
content: 'Hello, world!',
},
],
},
],
},
],
},
],
}
위 예시에서 볼 수 있듯이, DOM은 웹 페이지의 구조를 트리 구조로 표현한 것입니다. document.querySelector 혹은 document.getElementById 등의 메서드를 사용해 특정 노드를 검색하고 그 내용을 수정할 수 있습니다.
그러나 위처럼 단순한 코드도 꽤 depth가 깊어지듯이 복잡한 어플리케이션 상태에서 직접 DOM을 조작하는 것은 많은 문제를 야기합니다.
예를 들어 button 하나를 클릭해 카운트를 증가시키려면 해당 button 요소를 DOM에서 일일이 조회하여 찾은 후에 카운트 숫자를 증가시켜야 합니다. 하나만 해도 꽤 시간이 걸릴텐데 만약 이를 여러 번 수행한다면..?
그렇다면 정말 쉽지 않을 것입니다. 이는 사용자 경험 측면에서 굉장한 불편함을 가져오기 때문에 지양해야하는 일이기도 합니다. 또 브라우저마다 약간의 차이가 있어 호환성 문제도 야기합니다. 이는 다양한 환경에서도 동일한 결과를 제공해야 하는 개발자들에겐 최악이기도 합니다.
그래서 이를 해결하고자 React는 Virtual DOM 즉, 가상돔을 도입합니다.
메모리의 DOM의 가상 표현을 만들고 나면, 실제 DOM을 직접 수정하지 않고도 가상 표현을 변경할 수 있습니다. 이를 통해 일일이 페이지의 레이아웃을 다시 계산하고 엘리먼트를 다시 그리는 작업을 줄일 수 있습니다.
React는 가상 DOM을 사용해 사용자 인터페이스를 구축합니다. 그렇다면 어떻게 구현이 될까요? 먼저 핵심 개념 중 하나인 리액트 엘리먼트 부터 알아보겠습니다.
React에서 사용자 인터페이스는 컴포넌트 또는 HTML 엘리먼트의 가벼운 형태인 리액트 엘리먼트의 트리로 표현됩니다. 즉, React.createElement 함수를 사용해 생성되며 엘리먼트를 중첩해 복잡한 사용자 인터페이스를 만들 수 있습니다.
const element = React.createElement(
'div',
{
className: 'container',
},
'Hello, world!',
)
위 코드를 보시면 아시다시피 리액트 엘리먼트는 리액트 애플리케이션의 가장 작은 구성 블록으로 화면에 표시되는 요소를 나타냅니다.
React.createElement 함수와 DOM에 내장된 document.createElement 함수는 모두 새로운 엘리먼트를 생성한다는 점에서 유사하지만 전자는 리액트 엘리먼트를, 후자는 DOM 노드를 생성합니다.
이것은 React의 가상 DOm 또한 트리 같은 엘리먼트 구조를 표현한다는 점에서 실제 DOM과 개념이 유사하다는 것을 알 수 있습니다.
그래서 살펴보면 리액트 컴포넌트가 렌더링되면 리액트는 새 가상 DOM 트리를 생성하고 이전 가상 DOM 트리와 비교 후, 이전 트리를 새 트리에 일치하도록 업데이트하는 데 필요한 최소 변경 횟수를 계산합니다.
이를 재조정 프로세스라고 합니다. 또한 이전 트리와 새 트리를 노드 별로 비교해 어느 부분이 변경되었는지 알아내는 작업을 우리는 디핑(diffing) 이라고 합니다.
위와 같은 원리 등으로 React는 업데이트 변경을 최소화해 실제 DOM을 빠르게 업데이트 해주며 이를 통해 애플리케이션 성능을 개선하고 복잡하고 다양한 사용자 인터페이스를 구축할 수 있습니다.
가상 DOM의 장점을 정리하자면 아래와 같습니다.
즉, UI를 빠르고 부드럽게 만들 수 있는 핵심 원리 라고 할 수 있습니다.
그러나 위와 같은 장점들 속에도 아직 넘어야 할 한계는 있습니다. 바로 불필요한 리렌더링인데요
리렌더링이란 다시 렌더링 즉, 리액트가 각 함수 컴포넌트를 재귀적으로 호출하면서 프롭을 인수로 각 함수 컴포넌트에 전달한다는 의미입니다.
그러나 이는 리액트의 설계 때문에 발생할 수 밖에 없는 문제이긴 합니다.
리액트는 컴포넌트 상태가 변경되면 컴포넌트와 모든 자손 컴포넌트를 렌더링합니다. 설령 상태가 변경되지 않은 컴포넌트가 있을지라도 말입니다.
그래서 위와 같은 문제를 해결하기 위해 React.memo , useMemo , useCallback 등의 방법으로 최대한 리렌더링을 줄이려고 노력하고 있습니다.
직접 DOM을 조작하지 않고 성능을 최적화 하기 위해 제시된 가상 DOM은 한계가 있지만 그걸 상쇄하는 많은 장점을 가지고 있습니다. 보안과 브라우저 호환성 뿐 아니라 복잡한 사용자 인터페이스 구축에도 큰 도움을 주고 있습니다. 가상 DOM에 대해 이해하는 것은 프론트엔드 개발자가 반드시 가져야 할 숙명이라고 생각합니다. 왜 가상 DOM이 나왔는지를 고민해보며 리액트 본연의 가치를 잘 활용할 수 있는 코드를 작성할 수 있는 개발자가 되기 위해 노력해야겠다는 결심을 하며 다음 포스트에서 마저 이어나가도록 하겠습니다.