Virtual DOM

virtual DOM์€ ๋ฆฌ์•กํŠธ๊ฐ€ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์œ ๋ช…ํ•ด์กŒ๋‹ค. ์ด ๊ธ€์—์„œ virtual DOM์ด ๋ฌด์—‡์ธ์ง€, DOM๊ณผ๋Š” ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ์ง€, ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค.

์™œ virtual DOM์ด ํ•„์š”ํ• ๊นŒ?

DOM์€ ๋‘ ํŒŒํŠธ๋กœ ๋‚˜๋‰˜์–ด์ง„๋‹ค.

  1. ๊ฐ์ฒด ๊ธฐ๋ฐ˜์˜ HTML document ํ‘œํ˜„

  2. ๊ทธ ๊ฐ์ฒด๋ฅผ ์กฐ์ž‘ํ•˜๋Š” API

๋งŒ์•ฝ ์•„๋ž˜์˜ HTML์—์„œ ์ฒซ ๋ฒˆ์จฐ ์•„์ดํ…œ์˜ ์ด๋ฆ„์„ "List item one"์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์•„์ดํ…œ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

<!doctype html>
<html lang="en">
 <head></head>
 <body>
    <ul class="list">
        <li class="list__item">List item</li>
    </ul>
  </body>
</html>
const list = document.querySelector(".list")
const firstItem = list.children[0]
firstItem.textContent = 'List item one';
const secondItem = document.createElement('li');
secondItem.classList.add('list__item');
secondItem.textContent = 'List item two';
list.appendChild(secondItem)

DOM์ด 1998๋…„์— ์ฒ˜์Œ ๋‚˜์™”์„ ๋•Œ๋Š” ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ์„ ์ƒ์„ฑํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•˜๋Š”๋ฐ DOM API๋ฅผ ํ›จ์”ฌ ์ ๊ฒŒ ์˜์กดํ•˜์˜€๋‹ค.

์ž‘์€ ๋ฒ”์œ„์—์„œ ๊ฐ„๋‹จํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ดœ์ฐฎ์ง€๋งŒ, ๋งค ์ดˆ๋งˆ๋‹ค ํŽ˜์ด์ง€์˜ ์—ฌ๋Ÿฌ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋ฐ”๊พผ๋‹ค๋ฉด, ์ง€์†์ ์ธ query์™€ DOM์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๋น„์šฉ์ด ๋งŽ์ด ๋“ค๊ฒŒ ๋œ๋‹ค.

๊ฒŒ๋‹ค๊ฐ€, ๋„“์€ ๋ฒ”์œ„๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋น„์šฉ์ด ํฐ operation์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด, ํŠน์ • ๋ถ€๋ถ„์„ ์ฐพ์•„์„œ updateํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๊ฐ„๋‹จํ•˜๋‹ค. ์•„๋ž˜์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ innerHTML๋กœ ์ „์ฒด list๋ฅผ ๊ต์ฒดํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋” ๊ฐ„๋‹จํ•˜๋‹ค.

const list = document.querySelector(".list")
list.innerHTML = `
    <li class='list__item'>list item one</li>
    <li class='list__item'>list item two</li>
`

์ด ์˜ˆ์ œ์—์„œ๋Š” ๋‘ ๋ฐฉ๋ฒ•์—์„œ์˜ ์„ฑ๋Šฅ ์ฐจ์ด๊ฐ€ ํฌ๊ฒŒ ๋‘๋“œ๋Ÿฌ์ง€์ง€๋Š” ์•Š๋Š”๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์›น ํŽ˜์ด์ง€๊ฐ€ ์ปค์ง์— ๋”ฐ๋ผ, ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์ฐพ์•„๋‚ด์„œ ์—…๋ฐ์ดํŠธํ•˜๋Š”๊ฒŒ ๋” ์ค‘์š”ํ•ด์ง„๋‹ค.

VDOM์€ ๊ทธ๊ฑธ ํ•ด๋ƒ…๋‹ˆ๋‹ค...

VDOM์€ DOM์„ ์ž์ฃผ ์—…๋ฐ์ดํŠธํ•ด์•ผํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ์ข€ ๋” ์„ฑ๋Šฅ์ด ์ข‹์€ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ๋‹ค. DOM์ด๋‚˜ shadow DOM๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ, VDOM์€ ๊ณต์‹์ ์ธ ๋ช…์„ธ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

VDOM์€ original DOM์˜ ๋ณต์‚ฌ๋ณธ์œผ๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ์ด ๋ณต์‚ฌ๋ณธ์ด DOM API ์—†์ด ์ž์ฃผ ์กฐ์ž‘๋˜๊ณ  ์—…๋ฐ์ดํŠธ๋œ๋‹ค. VDOM์— ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด, original DOM์—์„œ ์–ด๋– ํ•œ ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜์•ผํ•˜๋Š”์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ๊ทธ ์˜์—ญ์„ ํŠน์ •ํ•˜์—ฌ ์ตœ์ ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

VDOM์€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋‚˜

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

์ด๋Ÿฌํ•œ ๊ฐ์ฒด๋ฅผ VDOM์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. DOM์ฒ˜๋Ÿผ HTML document๋ฅผ ๊ฐ์ฒด ๊ธฐ๋ฐ˜์œผ๋กœ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์ด๋Š” ์ˆœ์ˆ˜ JS ๊ฐ์ฒด์ด๋ฏ€๋กœ, ์ž์œ ๋กญ๊ณ  ์ž์ฃผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ์‹ค์ œ DOM์„ ๊ฑด๋“ค์ด์ง€ ์•Š์œผ๋ฉด์„œ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์ „์ฒด ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค, VDOM์˜ ์ž‘์€ ๋ถ€๋ถ„์— ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ผ๋ฐ˜์ ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์€ list ์ปดํฌ๋„ŒํŠธ๋กœ ์ž‘์—…์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

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

VDOM์˜ ์•„๋ž˜์—์„œ๋Š”

VDOM์€ DOM์˜ ์„ฑ๋Šฅ๊ณผ ์‚ฌ์šฉ์„ฑ ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊นŒ?

VDOM์€ DOM์—์„œ ํ•„์š”ํ•œ ๋ณ€ํ™”๋ฅผ ์ฐพ์•„๋‚ด๊ณ , ํŠน์ •ํ•œ ๋ถ€๋ถ„์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ VDOM์„ ์‚ฌ์šฉํ•œ๋‹ค.

1. VDOM์˜ copy๋ฅผ ๋งŒ๋“ค๊ธฐ

๋จผ์ € ํ• ์ผ์€ VDOM์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์—๋Š” ๋ณ€ํ™”๋ฅผ ์ฃผ์–ด์•ผํ•˜๋Š” ๋ถ€๋ถ„๋“ค์ด ์ ์šฉ๋˜์–ด์žˆ๋‹ค(list item two๊ฐ€ ์ถ”๊ฐ€๋จ!). ์‹ค์ œ 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"
        }
    ]
};

์ด copy๋Š” ์›๋ž˜์˜ VDOM(์—ฌ๊ธฐ์„œ๋Š” list)์™€ ๋น„๊ตํ•˜์—ฌ 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๋ฅผ ํ™•์ธํ•˜์—ฌ, ์ƒˆ๋กœ์šด child๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ๊ฒƒ์„ ๋ณ€๊ฒฝํ•˜๋Š” ์‹์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

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);
    }
})

์ถœ์ฒ˜

Last updated