처음부터 간단한 가상 DOM을 설명하고 생성했습니다.

가상 DOM에 대해 처음 들었을 때 어떻게 작동하는지, 어떻게 나만의 가상 DOM을 만들 수 있는지 궁금했습니다. 약간의 연구와 연습을 한 후에 제가 만든 가상 돔을 보여드리겠습니다.

돔은 무엇입니까?



DOM(Document Object Model)은 프로그래머와 사용자가 문서를 보다 쉽게 ​​볼 수 있도록 구조화된 계층 방식으로 웹 페이지를 표현하는 방법입니다. DOM을 사용하면 문서 개체에서 제공하는 명령이나 메서드를 사용하여 태그, ID, 클래스, 속성 또는 요소에 쉽게 액세스하고 조작할 수 있습니다.

객체 모델이라고 부르는 이유는 무엇입니까?



문서는 객체를 사용하여 모델링되며, 모델에는 문서의 구조뿐만 아니라 문서의 동작과 HTML의 속성을 가진 유사한 태그 요소로 구성된 객체가 포함됩니다.

DOM의 구조:



DOM은 트리 또는 포리스트(둘 이상의 트리)로 생각할 수 있습니다. 구조 모델이라는 용어는 때때로 문서의 트리형 표현을 설명하는 데 사용됩니다. DOM 구조 모델의 중요한 속성 중 하나는 구조적 동형입니다. 동일한 문서의 표현을 생성하기 위해 두 개의 DOM 구현을 사용하는 경우 정확히 동일한 개체 및 관계를 사용하여 동일한 구조 모델을 생성합니다.



More information

가상 DOM이란 무엇입니까?



가상 DOM은 개체의 실제 DOM 요소를 메모리 내에서 표현한 것입니다. 예시:

const myButton = {
    tagName: 'button',
    attrs: {
        id: 'btn',
        class: 'save-btn'
    },
    children: ['save']
};


html 등가물


  <button id="btn" class="save-btn">save</button>



이 모든 것을 이해하고 시작합시다 😊



요소를 나타내는 객체를 생성하고 이 객체를 반환하는 함수가 필요합니다.

// createElement.js

function createElement(tagName, { attrs = {}, children = [] } = {}){

    return {
        tagName,
        attrs,
        children
    }
}

export default createElement;


이제 요소를 렌더링하는 함수를 만들어야 합니다.

// render.js

function render({ tagName, attrs = {}, children = [] }){
    let element = document.createElement(tagName);
        // insert all children elements
        children.forEach( child =>  {
            if (typeof child === 'string'){
               // if the children is a kind of string create a text Node object
                element.appendChild(document.createTextNode(child));
            }
            else {
                // repeat the process with the children elements
                element.appendChild(render(child));
                }
            });
      // if it has attributes it adds them to the element
    if (Object.keys(attrs).length){
        for (const [key, value] of Object.entries(attrs)) {
            element.setAttribute(key, value);
        }
    }

    return element;
};

export default render;


그런 다음 요소를 DOM에 삽입하는 함수를 만듭니다.

// insert.js

function insertElement(element, domElement){
    domElement.replaceWith(element);
    return element;
}

export default insertElement;


이제 도구가 준비되었으니 사용해 봅시다!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>my vDOM</title>
</head>
<body>
    <div id="root">
    </div>
    <script src="./main.js" type="module"></script>
</body>
</html>



// main.js

import createElement from './createElement.js';
import render from './render.js';
import insertElement from './insert.js';

let myVirtualElement = createElement("div", {
  attrs: { id: "container" },
  children: [
    createElement("p", {
      attrs: { id: "text" },
      children: ["hello world"],
    }),
  ]
});

let element = render(myVirtualElement);
let rootElemet = insertElement(element, document.querySelector('#root'));



모든 웹 서버에서 이것을 실행하십시오. 나는 vscode에서 라이브 서버로 실행합니다.



우리는 그것을 얻었다! 🥳

이제 Post에서 만든 가상 요소 사이의 차이를 만드는 알고리즘을 사용하여 더 흥미롭게 만들 수 있습니다.

// diff.js

import render from './render.js';

const zip = (xs, ys) => {
  const zipped = [];
  for (let i = 0; i < Math.max(xs.length, ys.length); i++) {
    zipped.push([xs[i], ys[i]]);
  }
  return zipped;
};

const diffAttrs = (oldAttrs, newAttrs) => {
  const patches = [];

  // set new attributes
  for (const [k, v] of Object.entries(newAttrs)) {
    patches.push($node => {
      $node.setAttribute(k, v);
      return $node;
    });
  }

  // remove old attributes
  for (const k in oldAttrs) {
    if (!(k in newAttrs)) {
      patches.push($node => {
        $node.removeAttribute(k);
        return $node;
      });
    }
  }

  return $node => {
    for (const patch of patches) {
      patch($node);
    }
  };
};

const diffChildren = (oldVChildren, newVChildren) => {
  const childPatches = [];
  oldVChildren.forEach((oldVChild, i) => {
    childPatches.push(diff(oldVChild, newVChildren[i]));
  });

  const additionalPatches = [];
  for (const additionalVChild of newVChildren.slice(oldVChildren.length)) {
    additionalPatches.push($node => {
      $node.appendChild(render(additionalVChild));
      return $node;
    });
  }

  return $parent => {
    for (const [patch, child] of zip(childPatches, $parent.childNodes)) {
      patch(child);
    }

    for (const patch of additionalPatches) {
      patch($parent);
    }

    return $parent;
  };
};

const diff = (vOldNode, vNewNode) => {
  if (vNewNode === undefined) {
    return $node => {
      $node.remove();
      return undefined;
    };
  }

  if (typeof vOldNode === 'string' || typeof vNewNode === 'string') {
    if (vOldNode !== vNewNode) {
      return $node => {
        const $newNode = render(vNewNode);
        $node.replaceWith($newNode);
        return $newNode;
      };
    } else {
      return $node => undefined;
    }
  }

  if (vOldNode.tagName !== vNewNode.tagName) {
    return $node => {
      const $newNode = render(vNewNode);
      $node.replaceWith($newNode);
      return $newNode;
    };
  }

  const patchAttrs = diffAttrs(vOldNode.attrs, vNewNode.attrs);
  const patchChildren = diffChildren(vOldNode.children, vNewNode.children);

  return $node => {
    patchAttrs($node);
    patchChildren($node);
    return $node;
  };
};

export default diff;


이제 우리는 main.js를 변경합니다

// main.js

import createElement from './createElement.js';
import render from './render.js';
import insertElement from './insert.js';
import diff from './diff.js';

let myElement = createElement('div', {
    attrs: { class: 'container'},
    children: [createElement('img', {
        attrs: { id: 'img', src: 'https://i.picsum.photos/id/1/200/300.jpg' },
        children: []
    })]
})


let element = render(myElement);
let rootElemet = insertElement(element, document.querySelector('#root'));

let count = 0;

setInterval(()=> {
    count += 1;
    let myVirtualElemet = createElement('div', {
        attrs: { class: 'img'},
        children: [createElement('img', {
            attrs: { id: 'img', src: `https://i.picsum.photos/id/${count}/200/300.jpg` },
            children: []
        })]
    })

    const patch = diff(myElement, myVirtualElemet);

    rootElemet = patch(rootElemet);


    myElement = myVirtualElemet;

}, 1000);



실행 🤞



우리는 그것을 얻었다! 🥳

매초 링크 내부의 새 ID로 src 속성을 변경하여 DOM의 변경 사항을 업데이트하고 적용합니다.

영어를 못해서 글을 너무 못써서 죄송합니다

좋은 웹페이지 즐겨찾기