DOM(Document Object Model)은 HTML를 파싱해 문서의 구조, 스타일, 내용을 구조화 해 나타내고, 이걸 제어할 수 있는 API를 제공한다.
HTML 요소는 렌더링 엔진에 의해 파싱되고 DOM을 구성하는 요소 노드 객체로 변환된다. 요소 간의 부자 관계를 반영해 객체화한 모든 노드 객체들을 트리 자료구조로 저장된다.
<div class="greeting">Hello</div>
// div -> element node // class -> attribute name (attribute node) // "greeting"
-> attribute value (attribute node) // Hello -> Content (text node)돔은 노드 타입에 따라 필요한 기능을 프로퍼티와 메소드의 집합인 DOM API로 제공한다. 이 돔 API를 통해 HTML의 구조나 내용, 스타일을 동적으로 조작할 수 있다.
보통은 요소 노드를 취득해 조작한다.
요소 노드는 id를 통해 취득하거나 태그이름, css 선택자 등을 이용한다.
ex) getElementById, getElementByTagName, * { ... }, p { ... }, #foo { ... }
요새 많이 쓰는 리액트는 버추얼 돔을 쓴다. 그럼 리액트는 왜 버추얼 돔을 쓸까?
3줄 요약
- SPA에선 DOM 조작이 많고 조작이 있을 때마다 render tree가 재생성되고, 요소들의 스타일의 재계산, layout 만들고, painting 과정이 많이 발생**(즉, 레이아웃, 트리 변화, 렌더링 등으로 연산 ↑)**
- 연산 비용을 줄이기 위해, 변화가 일어날 때마다 버추얼 돔에 적용하고, 모든 연산이 끝난 후 1번만 실제 DOM에 전달한다.(물론 레이아웃 계산과 리렌더링 규모는 커진다 → 최적화 필요) ⇒ 이렇게 연산비용을 줄임
- 버추얼 돔 없이도 2번과 같은 과정이 가능하나, 버추얼 돔을 사용하면 이 과정을 자동화, 추상화할 수 있다.(컴포넌트끼리 상호작용 할 필요가 없어 동기화 작업이 필요하지 않음)
일단 브라우저의 workflow를 알면 좋다.(브라우저의 작동 원리)
앞에 나왔듯이 대부분의 브라우저는 비슷하게 동작한다.
브라우저는 HTML을 받으면 렌더 엔진으로 이걸 파싱하고 DOM 노드로 이뤄진 트리를 만든다. 각 노드는 각 HTML 엘리먼트들과 연관되어 있다.
외부 CSS 파일과 각 엘리먼트의 inline 스타일을 파싱하고 스타일 정보를 사용해 DOM 트리에 따라 새로운 Render tree를 생성한다.
DOM 트리의 모드 노드들은 attach라는 메소드가 있다. 이 메소드는 스타일 정보를 계산해 객체 형태로 반환하고 동기적으로 이뤄진다. DOM 트리에 새로운 노드가 추가되면 그 노드의 attach 메소드가 실행된다. 렌더 트리를 만드는 과정에 각 요소들의 스타일이 계산되고, 또 이 계산되는 과정에서 다른 요소들의 스타일 속성들을 참조한다.
렌더 트리가 다 만들어지면 레이아웃 과정을 거친다.(reflow) 각 노드들의 스크린 좌표가 주어지고 어디에 나타야 할지 위치가 주어진다.
그 다음 렌더링된 요소들에 painting이 이뤄진다. 트리의 각 노드들을 거쳐가면서 print() 메소드를 호출하고 스크린에 원하는 정보가 나타난다.
정리하자면,
DOM에 변화가 생기면 렌더트리를 재생성하고 → 그럼 모든 요소들의 스타일이 다시 계산되고 → 레이아웃을 만들고 → 페인팅을 하는 과정이 반복된다.
문제는,
SPA의 경우 DOM 조작이 많이 발생하고 그러다 보니 브라우저에 많은 연산이 필요하다. 그러다보니 전체적인 프로세스가 비효율적이다. (계속 DOM 조작하면서 연산이 이뤄지기 때문에)
DOM 조작의 문제는 1개의 조작이 레이아웃 변화, 트리 변화와 렌더링을 만든다는 점이다. 예를 들어, 30개의 노드를 하나하나 수정하면, 30번의 레이아웃 재계산과 30번의 리렌더링이 발생한다.
버추얼 돔은,
버추얼 돔은 일종의 DOM의 버퍼링이라고 생각하면 된다.
변화가 일어나면 오프라인 DOM 트리에 적용시키고 이 DOM 트리는 렌더링이 되지 않기 때문에 연산 비용이 적다. 연산이 끝난 후 그 최종적인 변화를 실제 DOM에 던져 모든 변화를 하나로 묶어서 연산의 횟수를 줄인다.
물론 레이아웃 계산과 리렌더링의 규모는 커지긴 한다.
이 과정은 버추얼 돔이 없어도 이뤄질 수 있긴 하다. 변화가 있을 때, 그 변화를 묶어서 DOM fragment에 적용한 다음 기존 DOM에 던져주면 된다.
그럼에도 버추얼 돔이 쓰이는 이유는?
DOM fragment를 관리하는 걸 수동으로 하나하나 작업할 필요 없이 자동화하고 추상화한다.
즉, DOM 관리를 버추얼 돔이 하게 해서 컴포넌트가 DOM 조작을 할 때 다른 컴포넌트들과 상호작용할 필요가 없고, 특정 DOM을 조작할 것이라던지의 정보를 공유할 필요가 없다. 각 변화들의 동기화 작업을 거치지 않으면서 모든 작업을 하나로 묶어줄 수 있다.
