DOM
돔(DOM)이란 Document Object Model의 약자이며, HTML 및 XML 문서를 위한 API이다. 돔은 객체 간의 관계를 지정하지 않았기 때문에 논리적 구조라고 하며, 개발자는 DOM을 사용하여 문서를 작성하고 구조를 탐색하며, 돔에 요소 또는 내용을 추가, 수정, 삭제할 수 있다.
Dom을 조금 더 이해하려면 Data Structure Tree(데이터 트리 구조)에 대해 이해 해야 한다.
Data Structure Tree
트리의 최상위 데이터(Root, 루트)는 매우 중요하거나 그 아래 다른 요소를 포함하기 위해 존재한다. 루트는 매우 중요한 역할을 하며, 트리에서 추출할 정보를 검색할 수 있는 공간을 제공한다.
DOM Parser
모든 브라우저에는 HTML 문서를 DOM으로 구문 분석하는 DOM Parser(돔 파서)가 있다. 돔 파서는 HTML 페이지를 읽고 해당 데이터를 DOM을 구성하는 객체로 바꾼다. 그리고 논리적으로 DOM Structure Tree로 배열한다.
DOM 트리에서 각 HTML 요소를 Node(노드)라고 한다. 위 그림에서 <html> 태그는 루트 노드이다. 맨 위에서 말했듯이 DOM을 통해 HTML 문서에 액세스 할 수 있으며 조작할 수도 있다. 그리고 DOM Tree에서 노드를 변경하거나 업데이트할 때마다 웹 페이지에 반영된다.
DOM Manipulation
돔 조작(DOM Manipulation)은 웹 페이지를 수정하기 위해 매우 유용하지만, 문제점도 존재한다. 만약 자바스크립트를 사용해서 <div> 태그의 색상을 업데이트한다고 하면 해당 DOM node 객체에 접근하고 색상 속성을 업데이트하면 된다. 이때는 트리의 나머지 노드에 영향을 미치지 않는다.
그러나 트리에서 하나의 노드를 추가하거나 제거한다면, 전체 트리를 다시 정렬해야 할 수도 있다. 이것은 비용이 많이 드는 작업이며, 브라우저에서는 시간과 브라우저 리소스가 필요하다. 예를 들어 DOM에 5가지 리스트(<li></li>)가 추가된다고 하면, 하나의 리스트마다 새 노드가 DOM에 추가되어 트리가 매번 업데이트된다. 총 5개의 업데이트가 추가되는 것이다.
같은 예로 하나의 노드 추가나 삭제를 하여 웹 페이지 전체의 레이아웃에 영향을 받는 경우, 웹페이지의 일부 또는 전체가 다시 렌더링 될 수 있다. 이런 경우를 Reflow라고 한다. 다시 말하자면 대화식 사이트(interactive site)에서 업데이트한 후에 브라우저가 웹 페이지의 일부 또는 전부를 다시 처리하고 그려야 할 때를 의미한다.
과도한 Reflow를 피하기 위해서는 Dom을 너무 많이 변경하면 안되며, 브라우저에 따라 다른 요소도 브라우저에 영향을 줄 수 있다.
하지만 대부분의 Javascript 프레임 워크가 DOM을 필요한 것보다 훨씬 많이 업데이트한다. 이러한 현상은 곧 속도 저하로 이어진다.
DOM 조작은 현대적인 대화식 웹(interactive site)의 핵심이다. 10개의 항목이 포함된 목록이 있는데 하나의 항목이 수정되었다면, javascript 프레임 워크는 대부분 전체 목록을 리렌더링한다. 물론 이렇게 작은 경우라면 크게 문제가 될 일이 없겠지만, 일반적인 웹사이트에서는 많은 양의 DOM을 조작할 수 있으며 비효율적인 업데이트가 발생한다.
A Browser's Workflow
(이 부분은 외부 글을 번역했으며, 해당 글의 내용에 의존합니다.)
위 다이어그램 및 설명은 Webkit 엔진의 용어를 사용한다. Workflow는 모든 브라우저에서 거의 비슷하기 때문이다.
1. DOM Tree가 생성된다 - 브라우저가 HTM: 파일을 수신하면 렌더 엔진은 해당 파일을 구문 분석하고 HTML 요소와 일대일 관계를 갖는 Node의 DOM 트리를 만든다.
2. Render Tree가 생성된다 - 인라인 스타일 구문과 외부 CSS 파일의 스타일이 구문 분석이 되며, DOM Tree의 node와 함께 Render Tree라는 다른 트리를 만드는 데 사용된다.
2-1. 위에서 언급한 Render Tree가 생성되기 위해서는 Render 객체의 시각적 속성을 계산해야 한다. DOM Tree의 모든 노드에는 계산된 스타일 정보를 가져오고 Render 객체를 반환하는 attach 메서드가 있다. Attachment는 동기식이며 동안 Attachment 메소드를 호출한다.
3. Layout(Reflow라고도 한다.) - Render Tree를 생성한 후에는 "Layout" 프로세스를 거친다. Render Tree의 모든 node에는 화면에 표시되어야 하는 정확한 위치인 좌표가 제공된다.
4. Painting(페인팅) - Render Tree가 통과되고 각 노드의 "paint()" 메소드가 호출되어 궁극적으로 화면에 내용이 표시된다.
위의 과정에서 알 수 있듯이, DOM을 만들 때마다 Render Tree의 생성부터 layout, painting까지 모두 다시 해야 한다. DOM은 빠르지만, 브라우저가 수행해야하는 레이아웃이 너무 많다(=느리다)! 여기에 대한 대안으로 React에서는 가상돔(Virtual Dom)을 사용한다.
Virtual DOM
실제 DOM의 변경사항에 대해 DOM에서 수행해야 할 모든 변경 사항을 가상돔(virtual DOM)에서 수행한 다음 실제 DOM에 전달함으로써 위에서 언급한 계산 단계가 줄어든다. 여러 번의 변경사항이 있더라도 모든 변경 사항을 하나로 그룹화하여 한번만 수행한다.
하지만, 잘 생각해보면 가상돔 없이도 해결할 수 있는 문제다. 왜냐하면 결국 가상돔도 "Rendering"하기 위해서 고유 DOM API인 "document.createDocumentFragment()"를 사용하기 때문이다. DOM의 모든 수정 사항을 직접 그룹화 한다음에 DOM에 넘겨도 되는 것이다.
그렇다면 가상돔은 무엇을 해결하는 것일까? DOM 관리를 자동화하고 추상화하여 직접 할 필요가 없게 해주는 것이다. 또한 전체 DOM Tree를 reload하지 않기 위해 변경한 부분과 변경되지 않는 부분을 직접 할 때는 추적해야 하나 이 또한 가상돔이 자동화해주는 것이다.
마지막으로 DOM 조작 자체를 포기함으로써 DOM을 수정하는 모든 부분 간의 동기화를 피할 수 있다.
여기서 다시 한번 제고해봐야 하는 점은 나는 분명히 위 글에서 "가상돔이 더 빠르다"라는 말을 하지 않았다는 점이다. 가상돔은 개발자가 작업을 보다 쉽게 할 수 있도록 도와주는 것이지, 가상돔에서 더 빠르게 접근할 수 있는 무언가를 제공해 주는 게 아니다. 이 부분은 React의 핵심 개발자들도 vanilla js가 가상돔에서 작업하는 것보다 항상 더 빠르다고 말하는 부분과 일맥상통하는 것이다. 가상돔은 목적을 위한 수단일 뿐이다라는 점만 기억하자!
Virtual DOM은 어떻게 생겼을까?
가상돔은 html 객체에 기반하여 자바스크립트 객체로 표현할 수 있다.
const vdom = {
tagName: "html",
children: [
{ tagName: "head" },
{
tagName: "body",
children: [
{
tagName: "div",
attributes: { "class": "img" },
children: [
{
tagName: "div",
attributes: { "class": "name" },
textContent: "name"
} // end div
]
} // end div
]
} // end body
]
} // end html
여기서 좋은 점은 저렇게 하나의 객체가 아닌 내가 작업하는 곳만으로 작게 쪼개서 작업 할 수 있다.
const div = {
tagName: "div",
attributes: { "class": "img" },
children: [
{
tagName: "div",
attributes: { "class": "name" },
textContent: "name"
} // end div
]
} // end div
가상돔은 쉽게 말해 DOM의 자바스크립트 객체로서의 표현이며, 작은 단위로 쪼개서 필요한 만큼 자주 수정할 수 있다. 그리고 변경되는 부분만 변경되게 하는 것은 직접 DOM API를 호출해도 되지만, React 같은 편리한 프레임워크를 이용해서 간단하게 해결할 수 있다.
React virtual DOM
react 애플리케이션을 렌더링할 때, 앱의 노드 트리가 메모리에 저장된다. 그 다음 트리는 다시 렌더링 환경으로 플러시(flush)된다. 그리고 앱이 업데이트되면(ex- setState) 새 Tree가 생성되고 이전 트리와 비교하여 랜더링 된 앱을 업데이트하는데 필요한 작업을 계산(=비교, diffing)하고, 실제 DOM은 변경된 내용만 업데이트 한다.
(cf) diffing - 사전 업데이트 된 virtual DOM과 업데이트 된 virtual DOM을 비교하는 과정을 diffing이라고 한다, React는 그래서 매번 두 개의 가상돔을 유지/관리한다.)
'React' 카테고리의 다른 글
React + Styled-components로 구현해본 아코디언(Accordion) (1) | 2020.07.26 |
---|---|
CSS in 리액트 - Inline Style (0) | 2020.02.01 |
[React] 리액트 (리)렌더링, React.memo (5) | 2020.01.31 |
react-intl 적용하기 (1) | 2020.01.11 |
[React Hooks] useMemo 사용하기 (0) | 2019.12.10 |