Articles

bitsofcode

Di recente ho scritto su cosa sono esattamente il DOM e il DOM ombra e come differiscono. Per ricapitolare, il Document Object Model è una rappresentazione a oggetti di un documento HTML e un’interfaccia per manipolare quell’oggetto. Il DOM ombra può essere pensato come una versione “leggera” del DOM. È anche una rappresentazione basata su oggetti di elementi HTML, ma non di un documento completo. Invece, il DOM ombra ci permette di separare il nostro DOM in bit più piccoli e incapsulati che possono essere usati attraverso i documenti HTML.

Un altro termine simile che potreste aver incontrato è il “DOM virtuale”. Anche se il concetto esiste da diversi anni, è stato reso più popolare dal suo uso nel framework React. In questo articolo, coprirò esattamente cos’è il DOM virtuale, come differisce dal DOM originale e come viene usato.

Perché abbiamo bisogno di un DOM virtuale?

Per capire perché è nato il concetto di DOM virtuale, rivisitiamo il DOM originale. Come ho detto, ci sono due parti del DOM – la rappresentazione basata su oggetti del documento HTML e l’API per manipolare quell’oggetto.

Per esempio, prendiamo questo semplice documento HTML con una lista non ordinata e un elemento della lista.

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

Questo documento può essere rappresentato come il seguente albero DOM:

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

Diciamo che vogliamo modificare il contenuto del primo elemento della lista in "List item one" e aggiungere anche un secondo elemento della lista. Per fare questo, dovremo usare le API del DOM per trovare gli elementi che vogliamo aggiornare, creare i nuovi elementi, aggiungere attributi e contenuti, e infine aggiornare gli stessi elementi del 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);

Il DOM non è stato fatto per questo…

Quando la prima specifica del DOM è stata rilasciata nel 1998, abbiamo costruito e gestito le pagine web in modo molto diverso. C’era molta meno fiducia nelle API del DOM per creare e aggiornare il contenuto della pagina così frequentemente come facciamo oggi.

Metodi semplici come document.getElementsByClassName() vanno bene per essere usati su piccola scala, ma se stiamo aggiornando più elementi in una pagina ogni pochi secondi, può iniziare a diventare davvero costoso interrogare e aggiornare costantemente il DOM.

Ancora di più, a causa del modo in cui le API sono impostate, di solito è più semplice eseguire operazioni più costose in cui aggiorniamo parti più grandi del documento che trovare e aggiornare gli elementi specifici. Tornando al nostro esempio di lista, è in qualche modo più facile sostituire l’intera lista non ordinata con una nuova che modificare gli elementi specifici.

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

In questo particolare esempio, la differenza di prestazioni tra i metodi è probabilmente insignificante. Tuttavia, man mano che la dimensione della pagina web cresce, diventa più importante selezionare e aggiornare solo ciò che è necessario.

… ma il DOM virtuale lo era!

Il DOM virtuale è stato creato per risolvere questi problemi di necessità di aggiornare frequentemente il DOM in un modo più performante. A differenza del DOM o del DOM ombra, il DOM virtuale non è una specifica ufficiale, ma piuttosto un nuovo metodo di interfacciamento con il DOM.

Un DOM virtuale può essere pensato come una copia del DOM originale. Questa copia può essere manipolata e aggiornata frequentemente, senza usare le API del DOM. Una volta che tutti gli aggiornamenti sono stati fatti al DOM virtuale, possiamo guardare quali cambiamenti specifici devono essere fatti al DOM originale e farli in modo mirato e ottimizzato.

Come è fatto un DOM virtuale?

Il nome “DOM virtuale” tende ad aggiungere mistero al concetto stesso. Infatti, un DOM virtuale è solo un normale oggetto Javascript.

Ripercorriamo l’albero DOM che abbiamo creato prima:

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

Questo albero può anche essere rappresentato come un oggetto Javascript.

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

Possiamo pensare a questo oggetto come al nostro DOM virtuale. Come il DOM originale, è una rappresentazione basata su oggetti del nostro documento HTML. Ma dato che è un semplice oggetto Javascript, possiamo manipolarlo liberamente e frequentemente senza toccare il DOM reale finché non ne abbiamo bisogno.

Invece di usare un oggetto per l’intero oggetto, è più comune lavorare con piccole sezioni del DOM virtuale. Per esempio, potremmo lavorare su un componente list, che corrisponderebbe al nostro elemento di lista non ordinata.

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

Sotto il cofano del DOM virtuale

Ora che abbiamo visto come appare un DOM virtuale, come funziona per risolvere i problemi di prestazioni e usabilità del DOM?

Come ho detto, possiamo usare il DOM virtuale per individuare i cambiamenti specifici che devono essere fatti al DOM e fare solo quegli aggiornamenti specifici. Torniamo al nostro esempio di lista non ordinata e facciamo le stesse modifiche che abbiamo fatto usando l’API DOM.

La prima cosa da fare è fare una copia del DOM virtuale, contenente le modifiche che vogliamo fare. Dato che non abbiamo bisogno di usare le API DOM, possiamo semplicemente creare un nuovo oggetto tutto insieme.

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

Questo copyviene usato per creare quello che viene chiamato un “diff” tra il DOM virtuale originale, in questo caso il list, e quello aggiornato. Un diff potrebbe assomigliare a questo:

const diffs = 

Questo diff fornisce istruzioni su come aggiornare il DOM attuale. Una volta che tutte le diff sono raccolte, possiamo raggruppare le modifiche al DOM, facendo solo gli aggiornamenti che sono necessari.

Per esempio potremmo fare un ciclo attraverso ogni diff e aggiungere un nuovo figlio o aggiornarne uno vecchio a seconda di ciò che la diff specifica.

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

Nota che questa è una versione molto semplificata e ridotta di come un DOM virtuale potrebbe funzionare e ci sono molti casi che non ho coperto qui.

Il DOM virtuale e i framework

È più comune lavorare con il DOM virtuale attraverso un framework, piuttosto che interfacciarsi direttamente con esso come ho mostrato nell’esempio precedente.

Frameworks come React e Vue usano il concetto di DOM virtuale per fare aggiornamenti più performanti al DOM. Per esempio, il nostro componente list può essere scritto in React nel seguente modo.

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

Se volessimo aggiornare la nostra lista, potremmo semplicemente riscrivere l’intero template della lista, e chiamare di nuovo ReactDOM.render(), passando la nuova lista.

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

Perché React usa il DOM virtuale, anche se stiamo ri-rendendo l’intero template, solo le parti che effettivamente cambiano vengono aggiornate. Se guardiamo i nostri strumenti di sviluppo quando avviene il cambiamento, vedremo gli elementi specifici e le parti specifiche degli elementi che cambiano.

Il DOM contro il DOM virtuale

Per ricapitolare, il DOM virtuale è uno strumento che ci permette di interfacciarci con gli elementi DOM in modo più facile e performante. Si tratta di una rappresentazione a oggetti Javascript del DOM, che possiamo modificare con la frequenza necessaria. Le modifiche apportate a questo oggetto vengono poi raccolte, e le modifiche al DOM reale sono mirate e fatte meno spesso.