Articles

bitsofcode

A közelmúltban írtam arról, hogy mi is pontosan a DOM és az árnyék DOM, és miben különböznek egymástól. Összefoglalva, a Document Object Model egy HTML dokumentum objektum alapú reprezentációja és egy interfész az objektum manipulálásához. Az árnyék DOM-ra úgy lehet gondolni, mint a DOM “könnyített” változatára. Ez szintén a HTML-elemek objektumalapú reprezentációja, de nem egy teljes önálló dokumentumé. Ehelyett az árnyék-DOM lehetővé teszi számunkra, hogy a DOM-ot kisebb, kapszulázott darabokra bontsuk, amelyek HTML-dokumentumokon keresztül használhatók.

Egy másik hasonló kifejezés, amellyel már találkozhatott, a “virtuális DOM”. Bár a koncepció már több éve létezik, a React keretrendszerben való használata tette népszerűbbé. Ebben a cikkben foglalkozom azzal, hogy pontosan mi is a virtuális DOM, miben különbözik az eredeti DOM-tól, és hogyan használják.

Miért van szükségünk virtuális DOM-ra?

Hogy megértsük, miért merült fel a virtuális DOM fogalma, térjünk vissza az eredeti DOM-ra. Mint említettem, a DOM-nak két része van – a HTML-dokumentum objektumalapú reprezentációja és az objektum manipulálására szolgáló API.

Vegyük például ezt az egyszerű HTML-dokumentumot egy rendezetlen listával és egy listaelemmel.

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

Ezt a dokumentumot a következő DOM-faként lehet ábrázolni:

  • html
    • head lang=”en”
    • body
      • ul class=”list”
        • li class=”list__item”
          • li class=”list__item”
            • “Listatétel”
            • “Listatétel”

Tegyük fel, hogy az első listaelem tartalmát szeretnénk módosítani "List item one"-ra és egy második listaelemet is szeretnénk hozzáadni. Ehhez a DOM API-kat kell használnunk a frissíteni kívánt elemek megkeresésére, az új elemek létrehozására, az attribútumok és a tartalom hozzáadására, majd végül maguknak a DOM-elemeknek a frissítésére.

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

A DOM nem erre készült…

Amikor 1998-ban megjelent a DOM első specifikációja, a weboldalakat nagyon másképp építettük és kezeltük. Sokkal kevésbé támaszkodtunk a DOM API-kra, hogy az oldal tartalmát olyan gyakran hozzuk létre és frissítsük, mint manapság.

Az olyan egyszerű módszerek, mint a document.getElementsByClassName(), kis léptékben jól használhatók, de ha néhány másodpercenként több elemet frissítünk egy oldalon, akkor nagyon költségessé válhat a DOM folyamatos lekérdezése és frissítése.

Sőt, az API-k beállítása miatt általában egyszerűbb olyan drágább műveleteket végezni, ahol a dokumentum nagyobb részeit frissítjük, mint megkeresni és frissíteni az egyes elemeket. Visszatérve a listás példánkhoz, bizonyos szempontból egyszerűbb a teljes rendezetlen listát egy újjal helyettesíteni, mint a konkrét elemeket módosítani.

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

Ebben a konkrét példában a módszerek közötti teljesítménykülönbség valószínűleg jelentéktelen. Azonban a weblap méretének növekedésével egyre fontosabbá válik, hogy csak azt válasszuk ki és frissítsük, amire szükségünk van.

… de a virtuális DOM volt!

A virtuális DOM azért jött létre, hogy ezeket a problémákat, vagyis a DOM gyakori frissítésének szükségességét nagyobb teljesítményű módon oldja meg. A DOM-tól vagy az árnyék-DOM-tól eltérően a virtuális DOM nem egy hivatalos specifikáció, hanem inkább a DOM-hoz való kapcsolódás új módszere.

A virtuális DOM-ra úgy lehet gondolni, mint az eredeti DOM másolatára. Ez a másolat gyakran manipulálható és frissíthető a DOM API-k használata nélkül. Miután az összes frissítést elvégeztük a virtuális DOM-on, megnézhetjük, hogy milyen konkrét változtatásokat kell végrehajtani az eredeti DOM-on, és azokat célzottan és optimalizált módon elvégezhetjük.

Milyen a virtuális DOM?

A “virtuális DOM” elnevezés hajlamos növelni a rejtélyt, hogy mi is ez a fogalom valójában. Valójában a virtuális DOM csak egy hagyományos Javascript objektum.

Menjünk vissza a korábban létrehozott DOM-fához:

  • html
    • head lang=”en”
    • body
      • ul class=”list”
        • li class=”list__item”
          • “Listatétel”

Ez a fa Javascript objektumként is ábrázolható.

const vdom = { tagName: "html", children: } // end ul ] } // end body ]} // end html

Ezt az objektumot tekinthetjük a virtuális DOM-unknak. Az eredeti DOM-hoz hasonlóan ez is a HTML-dokumentumunk objektumalapú reprezentációja. De mivel ez egy egyszerű Javascript-objektum, szabadon és gyakran manipulálhatjuk anélkül, hogy hozzáérnénk a tényleges DOM-hoz, amíg nem szükséges.

Ahelyett, hogy egy objektumot használnánk a teljes objektumra, gyakoribb, hogy a virtuális DOM kis szakaszaival dolgozunk. Például dolgozhatunk egy list komponenssel, amely a rendezetlen listaelemünknek felel meg.

const list = { tagName: "ul", attributes: { "class": "list" }, children: };

A virtuális DOM motorházteteje alatt

Most, hogy láttuk, hogyan néz ki a virtuális DOM, hogyan működik, hogy megoldja a DOM teljesítmény- és használhatósági problémáit?

Amint említettem, a virtuális DOM segítségével kiválaszthatjuk azokat a konkrét változtatásokat, amelyeket a DOM-on el kell végezni, és csak ezeket a konkrét frissítéseket tudjuk elvégezni. Térjünk vissza a rendezetlen listás példánkhoz, és végezzük el ugyanazokat a változtatásokat, mint a DOM API segítségével.

Az első dolog, amit tennénk, hogy készítünk egy másolatot a virtuális DOM-ról, amely tartalmazza azokat a változtatásokat, amelyeket el akarunk végezni. Mivel nem kell használnunk a DOM API-kat, tulajdonképpen egyszerűen létrehozhatunk egy teljesen új objektumot.

const copy = { tagName: "ul", attributes: { "class": "list" }, children: };

Ezt a copy-t arra használjuk, hogy létrehozzuk az úgynevezett “diff”-et az eredeti virtuális DOM, jelen esetben a list, és a frissített DOM között. A diff valahogy így nézhet ki:

const diffs = 

Ez a diff utasításokat ad a tényleges DOM frissítéséhez. Miután összegyűjtöttük az összes diffet, a DOM módosításait kötegenként elvégezhetjük, és csak a szükséges frissítéseket végezzük el.

Például végighaladhatunk minden diffen, és vagy hozzáadhatunk egy új gyermeket, vagy frissíthetünk egy régit attól függően, hogy a diff mit ír elő.

const domElement = document.getElementsByClassName("list");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); }})

Megjegyezzük, hogy ez egy nagyon leegyszerűsített és lecsupaszított változata annak, hogyan működhet egy virtuális DOM, és sok olyan eset van, amire itt nem tértem ki.

A virtuális DOM és a keretrendszerek

A virtuális DOM-mal gyakrabban dolgozunk egy keretrendszeren keresztül, mintha közvetlenül kapcsolódnánk hozzá, ahogy a fenti példában mutattam.

A keretrendszerek, mint például a React és a Vue, a virtuális DOM koncepcióját használják a DOM hatékonyabb frissítésére. Például a list komponensünk a Reactban a következő módon írható meg.

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

Ha frissíteni szeretnénk a listánkat, akkor egyszerűen újraírhatjuk a teljes listasablont, és újra meghívhatjuk a ReactDOM.render()-t, átadva az új listát.

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

Mivel a React a virtuális DOM-ot használja, annak ellenére, hogy a teljes sablont újra megjelenítjük, csak a ténylegesen változó részek frissülnek. Ha megnézzük a fejlesztői eszközeinket, amikor a változás megtörténik, látni fogjuk a konkrét elemeket és az elemek konkrét részeit, amelyek megváltoznak.

A DOM vs. a virtuális DOM

Még egyszer összefoglalva, a virtuális DOM egy olyan eszköz, amely lehetővé teszi számunkra, hogy a DOM elemekkel egyszerűbb és hatékonyabb módon kapcsolódjunk. Ez a DOM Javascript objektum reprezentációja, amelyet olyan gyakran módosíthatunk, amilyen gyakran csak szükségünk van rá. Az ezen az objektumon végrehajtott módosításokat ezután összesítjük, és a tényleges DOM-on végrehajtott módosításokat célzottan és ritkábban végezzük el.