Articles

bitsofcode

Nedávno jsem psal o tom, co přesně je DOM a stínový DOM a jak se liší. Shrňme si, že Document Object Model je objektová reprezentace dokumentu HTML a rozhraní pro manipulaci s tímto objektem. Stínový DOM si lze představit jako „odlehčenou“ verzi DOM. Jedná se také o objektovou reprezentaci prvků HTML, ale ne o plnohodnotný samostatný dokument. Místo toho nám stínový DOM umožňuje rozdělit náš DOM na menší zapouzdřené kousky, které lze použít napříč dokumenty HTML.

Dalším podobným termínem, se kterým jste se mohli setkat, je „virtuální DOM“. Ačkoli tento koncept existuje již několik let, větší popularitu mu přineslo jeho použití ve frameworku React. V tomto článku se budu zabývat tím, co přesně virtuální DOM je, jak se liší od původního DOM a jak se používá.

Proč potřebujeme virtuální DOM?

Abychom pochopili, proč koncept virtuálního DOM vznikl, vraťme se k původnímu DOM. Jak jsem již zmínil, DOM má dvě části – objektovou reprezentaci dokumentu HTML a rozhraní API pro manipulaci s tímto objektem.

Příklad vezměme tento jednoduchý dokument HTML s neuspořádaným seznamem a jednou položkou seznamu.

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

Tento dokument lze reprezentovat jako následující strom DOM:

  • html
    • head lang=“cs“
    • body
      • ul class=“list“
        • li class=“list__item“
          • „Položka seznamu“

Řekněme, že chceme upravit obsah první položky seznamu na "List item one" a také přidat druhou položku seznamu. K tomu budeme muset použít rozhraní DOM API, abychom našli prvky, které chceme aktualizovat, vytvořili nové prvky, přidali atributy a obsah a nakonec aktualizovali samotné prvky DOM.

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

Na to nebyl DOM stvořen…

Když byla v roce 1998 vydána první specifikace DOM, vytvářeli jsme a spravovali webové stránky zcela jinak. Na rozhraní API DOM se nespoléhalo zdaleka tak často při vytváření a aktualizaci obsahu stránky jako dnes.

Jednoduché metody, jako je document.getElementsByClassName(), lze v malém měřítku používat dobře, ale pokud aktualizujeme několik prvků na stránce každých několik sekund, může začít být neustálé dotazování a aktualizace DOM opravdu nákladné.

Ještě dále, vzhledem ke způsobu nastavení rozhraní API, je obvykle jednodušší provádět dražší operace, při kterých aktualizujeme větší části dokumentu, než vyhledávat a aktualizovat konkrétní prvky. Vrátíme-li se k našemu příkladu se seznamem, je v některých ohledech jednodušší nahradit celý neuspořádaný seznam novým, než upravovat konkrétní prvky.

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

V tomto konkrétním příkladu je výkonnostní rozdíl mezi metodami pravděpodobně zanedbatelný. S rostoucí velikostí webové stránky je však stále důležitější vybírat a aktualizovat pouze to, co je potřeba.

… ale virtuální DOM byl!“

Virtuální DOM byl vytvořen proto, aby tyto problémy s nutností časté aktualizace DOM řešil výkonnějším způsobem. Na rozdíl od DOM nebo stínového DOM není virtuální DOM oficiální specifikací, ale spíše novou metodou rozhraní s DOM.

Virtuální DOM si lze představit jako kopii původního DOM. S touto kopií lze často manipulovat a aktualizovat ji bez použití rozhraní API DOM. Po provedení všech aktualizací virtuálního DOM se můžeme podívat, jaké konkrétní změny je třeba provést v původním DOM, a provést je cíleně a optimalizovaně.

Jak vypadá virtuální DOM?

Název „virtuální DOM“ má tendenci přispívat k tajemství, co je to vlastně za koncept. Ve skutečnosti je virtuální DOM jen běžný objekt Javascriptu.

Vraťme se ke stromu DOM, který jsme vytvořili dříve:

  • html
    • head lang=“cs“
    • body
      • ul class=“list“
        • li class=“list__item“
          • „Položka seznamu“

Tento strom může být také reprezentován jako objekt Javascriptu.

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

Tento objekt si můžeme představit jako náš virtuální DOM. Stejně jako původní DOM je to objektová reprezentace našeho dokumentu HTML. Protože se však jedná o obyčejný objekt Javascriptu, můžeme s ním volně a často manipulovat, aniž bychom se dotkli skutečného DOM, dokud to nebudeme potřebovat.

Namísto používání jednoho objektu pro celý objekt je běžnější pracovat s malými částmi virtuálního DOM. Můžeme například pracovat s komponentou list, která by odpovídala našemu prvku neuspořádaného seznamu.

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

Pod kapotou virtuálního DOM

Teď, když jsme viděli, jak vypadá virtuální DOM, jak funguje při řešení problémů s výkonem a použitelností DOM?

Jak jsem již zmínil, pomocí virtuálního DOM můžeme vyčlenit konkrétní změny, které je třeba v DOM provést, a provést pouze tyto konkrétní aktualizace. Vraťme se k našemu příkladu neuspořádaného seznamu a proveďme stejné změny, které jsme provedli pomocí rozhraní DOM API.

Nejprve bychom vytvořili kopii virtuálního DOM, která by obsahovala změny, které chceme provést. Protože nemusíme používat rozhraní DOM API, můžeme vlastně úplně jednoduše vytvořit nový objekt.

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

Tento objekt copy slouží k vytvoření tzv. rozdílu mezi původním virtuálním DOM, v tomto případě list, a aktualizovaným objektem. Rozdíl může vypadat takto:

const diffs = 

Tento rozdíl poskytuje instrukce, jak aktualizovat skutečný DOM. Jakmile shromáždíme všechny rozdíly, můžeme dávkově měnit DOM a provádět jen ty aktualizace, které jsou potřeba.

Například bychom mohli procházet smyčkou každý rozdíl a buď přidat nového potomka, nebo aktualizovat starého v závislosti na tom, co rozdíl určuje.

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

Všimněte si, že toto je opravdu zjednodušená a okleštěná verze toho, jak by virtuální DOM mohl fungovat, a existuje mnoho případů, které jsem zde nepopsal.

Virtuální DOM a frameworky

S virtuálním DOM se častěji pracuje prostřednictvím frameworku, než aby se s ním komunikovalo přímo, jak jsem ukázal v příkladu výše.

Frameworky jako React a Vue používají koncept virtuálního DOM pro výkonnější aktualizace DOM. Například naši komponentu list lze v Reactu napsat následujícím způsobem.

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

Pokud bychom chtěli aktualizovat náš seznam, mohli bychom prostě přepsat celou šablonu seznamu a znovu zavolat ReactDOM.render() a předat nový seznam.

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

Protože React používá virtuální DOM, i když znovu vykreslujeme celou šablonu, aktualizují se pouze ty části, které se skutečně mění. Pokud se při změně podíváme do našich vývojářských nástrojů, uvidíme konkrétní prvky a konkrétní části prvků, které se mění.

Dom vs. virtuální DOM

Pokud to shrneme, virtuální DOM je nástroj, který nám umožňuje jednodušší a výkonnější rozhraní s prvky DOM. Jedná se o objektovou reprezentaci DOM v jazyce Javascript, kterou můžeme měnit tak často, jak potřebujeme. Změny provedené v tomto objektu se pak sčítají a úpravy skutečného DOM jsou cílené a provádějí se méně často.