분류 html

가상 DOM 이해

컨텐츠 정보

  • 조회 424 (작성일 )

본문

나는 최근 DOM과 그림자 DOM이 정확히 무엇이고 어떻게 다른지에 대해 글을 쓰고 있습니다. 요약하자면, 문서 객체 모델은 HTML 문서의 객체 기반 표현과 해당 객체를 조작하기 위한 인터페이스입니다. 

섀도우 DOM은 DOM의 "라이트"버전으로 생각할 수 있습니다. 또한 HTML 요소의 객체 기반 표현이지만 완전한 독립형 문서는 아닙니다. 대신 shadow DOM을 사용하면 DOM을 HTML 문서에서 사용할 수 있는 더 작고 캡슐화 된 비트로 분리 할 수 ​​있습니다.


https://bitsofco.de/understanding-the-virtual-dom/ 


또 다른 유사한 용어는 "가상 DOM"입니다. 이 개념은 몇 년 전부터 사용되었지만 React 프레임 워크에서 사용되면서 더욱 대중화되었습니다. 이 기사에서는 가상 DOM이 무엇인지, 원래 DOM과 어떻게 다른지, 어떻게 사용 되는지를 다룰 것입니다.


왜 우리는 가상 DOM이 필요합니까? 


가상 DOM의 개념이 발생한 이유를 이해하기 위해 원래 DOM을 다시 살펴 보겠습니다. 앞서 언급 했듯이 DOM에는 HTML 문서의 객체 기반 표현과 해당 객체를 조작하는 API의 두 부분이 있습니다.

예를 들어, 순서가 없는 목록과 하나의 목록 항목이 있는 이 간단한 HTML 문서를 살펴 보겠습니다.


<!doctype html>
<html lang="en">
 <head></head>
 <body>
    <ul class="list">
        <li class="list__item">List item</li>
    </ul>
  </body>
</html>


이 문서는 다음 DOM 트리로 표현 될 수 있습니다.

  • html
    • head lang="en"
    • body
      • ul class="list"
        • li class="list__item"
          • "List item"

첫 번째 목록 항목의 내용을 "목록 항목 1"로 수정하고 두 번째 목록 항목을 추가한다고 가정합니다. 이를 위해서는 DOM API를 사용하여 업데이트 할 요소를 찾고, 새 요소를 만들고, 속성과 내용을 추가 한 다음, DOM 요소 자체를 업데이트 해야 합니다.


const listItemOne = document.getElementsByClassName("list__item")[0];
listItemOne.textContent = "List item one";

const list = document.getElementsByClassName("list")[0];
const listItemTwo = document.createElement("li");
listItemTwo.classList.add("list__item");
listItemTwo.textContent = "List item two";
list.appendChild(listItemTwo);


DOM은 이것을 위해 만들어지지 않았습니다… 


1998 년 DOM의 첫 사양이 발표되었을 때 웹 페이지를 매우 다르게 구축하고 관리했습니다. 오늘날처럼 자주 페이지 컨텐츠를 작성하고 업데이트하는 DOM API에 대한 의존도는 훨씬 낮았습니다.


document.getElementsByClassName()과 같은 간단한 메소드는 소규모로 사용하는 것이 좋지만 몇 초마다 한 페이지에서 여러 요소를 업데이트하는 경우 DOM을 지속적으로 쿼리하고 업데이트하는 데 비용이 많이 들기 시작할 수 있습니다.


또한 API 설정 방식으로 인해 일반적으로 특정 요소를 찾아 업데이트하는 것보다 문서의 큰 부분을 업데이트하는 경우 더 비싼 작업을 수행하는 것이 더 간단합니다. 목록 예제로 돌아가서 특정 요소를 수정하는 것보다 정렬되지 않은 전체 목록을 새로운 목록으로 바꾸는 것이 더 쉬운 방법이 있습니다.


const list = document.getElementsByClassName("list")[0];
list.innerHTML = `
<li class="list__item">List item one</li>
<li class="list__item">List item two</li>
`;


이 특정 예에서, 방법들 간의 성능 차이는 중요하지 않을 것입니다. 그러나 웹 페이지의 크기가 커짐에 따라 필요한 항목만 선택하고 업데이트하는 것이 더욱 중요해졌습니다.


… 그러나 가상 DOM은! 


가상 DOM은 DOM을 보다 성능적으로 자주 업데이트 해야 하는 이러한 문제를 해결하기 위해 만들어졌습니다. DOM 또는 섀도우 DOM과 달리 가상 DOM은 공식 사양이 아니라 DOM과 인터페이스 하는 새로운 방법입니다.


가상 DOM은 원래 DOM의 복사본으로 생각할 수 있습니다. 이 사본은 DOM API를 사용하지 않고 자주 조작하고 업데이트 할 수 있습니다. 가상 DOM에 대한 모든 업데이트가 완료되면, 원래 DOM에 어떤 특정 변경이 필요한지 살펴보고 이를 대상으로 최적화 된 방식으로 만들 수 있습니다.


가상 DOM은 어떤 모습입니까? 


"가상 DOM"이라는 이름은 개념이 실제로 무엇인지에 대한 수수께끼에 추가되는 경향이 있습니다. 실제로 가상 DOM은 일반적인 Javascript 객체 일뿐입니다.


앞에서 만든 DOM 트리를 다시 살펴 보겠습니다.

  • html
    • head lang="en"
    • body
      • ul class="list"
        • li class="list__item"
          • "List item"

이 트리는 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"
                        } // end li
                    ]
                } // end ul
            ]
        } // end body
    ]
} // end html


이 객체를 가상 DOM으로 생각할 수 있습니다. 원래 DOM과 마찬가지로 HTML 문서의 객체 기반 표현입니다. 그러나 일반 자바 스크립트 객체이기 때문에 필요할 때까지 실제 DOM을 건드리지 않고 자유롭게 자주 조작 할 수 있습니다.


전체 객체에 대해 하나의 객체를 사용하는 대신 가상 DOM의 작은 섹션으로 작업하는 것이 더 일반적입니다. 예를 들어, 정렬되지 않은 목록 요소에 중점을 둔 목록 구성 요소에 대해 작업 할 수 있습니다.


const list = {
    tagName: "ul",
    attributes: { "class": "list" },
    children: [
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item"
        }
    ]
};


가상 DOM의 후드 아래 


이제 가상 DOM의 모습을 보았으므로 DOM의 성능 및 유용성 문제를 해결하기 위해 어떻게 작동합니까?


앞서 언급 했듯이 가상 DOM을 사용하여 DOM에 필요한 특정 변경 사항을 찾아 내고 해당 특정 업데이트만 만들 수 있습니다. 정렬되지 않은 목록 예제로 돌아가서 DOM API를 사용하여 변경한 것과 동일하게 변경해 보겠습니다.


가장 먼저 할 일은 변경하려는 내용이 포함 된 가상 DOM의 사본을 만드는 것입니다. DOM API를 사용할 필요가 없으므로 실제로 새 객체를 만들 수 있습니다.


const copy = {
    tagName: "ul",
    attributes: { "class": "list" },
    children: [
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item one"
        },
        {
            tagName: "li",
            attributes: { "class": "list__item" },
            textContent: "List item two"
        }
    ]
};


이 복사본은 원래 가상 DOM (이 경우 목록)과 업데이트 된 목록 사이에 "diff"라는 것을 만드는 데 사용됩니다. diff는 다음과 같이 보일 수 있습니다.


const diffs = [
    {
        newNode: { /* new version of list item one */ },
        oldNode: { /* original version of list item one */ },
        index: /* index of element in parent's list of child nodes */
    },
    {
        newNode: { /* list item two */ },
        index: { /* */ }
    }
]


이 diff는 실제 DOM을 업데이트하는 방법에 대한 지침을 제공합니다. 모든 diff가 수집 되면 DOM에 대한 변경 사항을 일괄 처리하여 필요한 업데이트 만 만들 수 있습니다. 예를 들어, 각 diff를 반복하고 diff가 지정한 내용에 따라 새 자식을 추가하거나 이전 자식을 업데이트 할 수 있습니다.


const domElement = document.getElementsByClassName("list")[0];

diffs.forEach((diff) => {

    const newElement = document.createElement(diff.newNode.tagName);
    /* Add attributes ... */

    if (diff.oldNode) {
        // If there is an old version, replace it with the new version
        domElement.replaceChild(diff.newNode, diff.index);
    } else {
        // If no old version exists, create a new node
        domElement.appendChild(diff.newNode);
    }
})


이것은 가상 DOM이 작동하는 방식에 대한 단순화되고 제거 된 버전이며 여기서 다루지 않은 많은 사례가 있습니다.


가상 DOM 및 프레임 워크 


위의 예에서 보여준 것처럼 직접 인터페이스 하지 않고 프레임 워크를 통해 가상 DOM으로 작업하는 것이 더 일반적입니다.


React 및 Vue와 같은 프레임 워크는 가상 DOM 개념을 사용하여 DOM의 성능을 향상 시킵니다. 예를 들어,리스트 컴포넌트는 다음과 같은 방식으로 React로 작성 될 수 있습니다.


import React from 'react';
import ReactDOM from 'react-dom';

const list = React.createElement("ul", { className: "list" },
    React.createElement("li", { className: "list__item" }, "List item")
);

ReactDOM.render(list, document.body);


목록을 업데이트 하려면 전체 목록 템플릿을 다시 작성하고 ReactDOM.render()를 다시 호출하여 새 목록을 전달하면 됩니다.


const newList = React.createElement("ul", { className: "list" },
    React.createElement("li", { className: "list__item" }, "List item one"),
    React.createElement("li", { className: "list__item" }, "List item two");
);

setTimeout(() => ReactDOM.render(newList, document.body), 5000);


React는 가상 DOM을 사용하기 때문에 전체 템플릿을 다시 렌더링 하더라도 실제로 변경되는 부분만 업데이트 됩니다. 변경 사항이 발생할 때 개발자 도구를 살펴보면 변경되는 요소의 특정 요소와 특정 부분이 표시됩니다.



 


DOM과 가상 DOM 


다시 말해, 가상 DOM은 보다 쉽고 성능이 우수한 방식으로 DOM 요소와 인터페이스 할 수 있는 도구입니다. DOM의 자바 스크립트 객체 표현으로, 필요한 만큼 자주 수정할 수 있습니다. 그런 다음 이 오브젝트에 대한 변경 사항이 조합되고 실제 DOM에 대한 수정 사항이 대상 지정되고 빈도가 줄어 듭니다.