Par

J'ai écrit dernièrement sur le DOM et sur le shadow DOM, et sur leurs différences. Pour faire court, le DOM (Document Object Model) est un objet représentant un document HTML et une interface permettant de manipuler cet objet. Le "DOM fantôme" (shadow DOM) est une version "allégée" du DOM. C'est aussi un objet représentant des éléments HTML, mais pas d'un document HTML complet. Le shadow DOM nous permet plutôt de diviser notre DOM en parties séparées, encapsulées, qui peuvent être utilisées et réutilisées dans des documents HTML.

Vous avez peut-être également entendu parler du DOM virtuel (virtual DOM). Bien que le concept existe depuis des années, il est devenu plus populaire lorsque React a commencé à l'utiliser. Dans cet article, je vais montrer ce qu'est exactement le virtual DOM, en quoi il diffère du DOM, et comment on l'utilise.

Pourquoi avons-nous besoin d'un DOM virtuel ?

Pour comprendre comment est né le concept de virtual DOM, revenons brièvement sur le DOM. Comme je l'ai indiqué, il y a deux aspects à considérer dans le DOM, d'une part la représentation du document HTML et d'autre part l'API qui manipule cet objet.

Par exemple, prenons ce document HTML simple, comprenant une liste non ordonnée et un item liste (<li>).

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

Ce document peut être représenté par l'arbre DOM suivant :

Admettons que nous voulions modifier le contenu du premier item liste pour qu'il devienne "List item one" et que nous voulions également ajouter un deuxième item liste. Pour cela, nous avons besoin de l'API DOM afin de trouver les éléments que nous voulons mettre à jour, créer les nouveaux éléments, ajouter les attributs et le contenu, et enfin mettre à jour les éléments du DOM eux-mêmes.

const listItemOne = document.getElementsByClassName("list__item")[0];
listItemOne.textContent = "List item one";

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

Le DOM n'a pas été conçu pour cela...

Quand la première spécification du DOM a été publiée en 1998, notre conception et notre gestion des pages web étaient différentes. On s'appuyait beaucoup moins sur les API du DOM pour créer et mettre à jour le contenu de nos pages et celui-ci ne changeait pas au même rythme qu'aujourd'hui.

L'utilisation de méthodes simples comme document.getElementsByClassName() est parfaite à petite échelle, mais si nous mettons à jour de nombreux éléments à chaque seconde, les requêtes et mises à jour constantes deviennent coûteuses.

De plus, en raison de la construction même des API, il est en général plus simple de réaliser des opérations coûteuses sur des ensembles plus vastes que d'aller chercher et mettre à jour les éléments spécifiques. Pour revenir à notre liste d'éléments, il peut être plus facile de remplacer toute la liste non ordonnée que d'en modifier quelques éléments.

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

Dans l'exemple ci-dessus, la différence de performance entre les méthodes est sans doute insignifiante. Cependant, plus le contenu de la page augmente et plus il devient important de ne sélectionner et mettre à jour que ce dont on a besoin.

... mais le DOM virtuel oui !

Le DOM virtuel a été créé pour résoudre ce genre de problèmes. Contrairement au DOM ou au DOM fantôme, le DOM virtuel n'est pas une spécification officielle mais plutôt une nouvelle façon d'interragir avec le DOM.

Un DOM virtuel peut être vu comme une copie du DOM original. Cette copie peut être manipulée et mise à jour fréquemment, sans utiliser les API DOM. Une fois toutes les mises à jour effectuées dans le DOM virtuel, nous pouvons voir quels changements doivent être apportés au DOM original et les réaliser d'une façon ciblée et optimisée.

À quoi ressemble un DOM virtuel ?

L'appellation "DOM virtuel" ajoute au mystère, alors qu'en réalité ce n'est qu'un objet JavaScript très classique. Reprenons notre arbre DOM précédent :

On peut aussi représenter cet arbre sous forme d'objet JavaScript :

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

On peut voir cet objet comme notre DOM virtuel. Tout comme le DOM original, il s'agit d'une représentation objet de notre document HTML. Mais comme c'est un simple objet JavaScript, nous pouvons le manipuler librement sans toucher au DOM jusqu'à ce que nous en ayons besoin.

Plutôt que d'utiliser un objet pour représenter l'objet entier, il est plus courant de travailler par petites sections du DOM virtuel. Par exemple, nous pourrions travailler sur un composant list qui correspondrait à notre élément liste non ordonnée.

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

Dans les coulisses du DOM virtuel

Maintenant que nous savons à quoi ressemble le DOM virtuel, intéressons-nous à son fonctionnement et à la façon dont il résout les problèmes de performance et d'usabilité du DOM.

Comme je l'ai dit, nous pouvons utiliser le DOM virtuel pour isoler les modifications spécifiques qui doivent être apportées au DOM et ne faire que ces mises à jour. Revenons à notre liste non ordonnée pour réaliser les mêmes changements que précédemment.

La première chose à faire est de réaliser une copie du DOM virtuel, contenant les changements que nous souhaitons opérer. Puisque nous n'avons pas besoin d'utiliser l'API DOM, nous pouvons simplement créer un nouvel objet.

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"
        }
    ]
};

Cette copie est utilisée pour créer ce qu'on appelle une "diff" entre le DOM virtuel original — dans ce cas la liste — et la nouvelle version. Une diff pourrait ressemble à ceci :

const diffs = [
    {
        newNode: { /* nouvelle version de list item one */ },
        oldNode: { /* version originale de list item one */ },
        index: /* index de l'élément in la liste des noeuds enfants du parent */
    },
    {
        newNode: { /* list item two */ },
        index: { /* */ }
    }
]

Cette diff fournit des instructions sur ce qui doit être mis à jour dans le DOM. Une fois toutes les diffs réunies, nous pouvons déclencher les modifications du DOM par batches, en ne réalisant que les mises à jour nécessaires.

Par exemple nous pouvons faire une boucle sur les diffs et soit ajouter un nouvel enfant, soit mettre à jour un enfant déjà existant, selon ce que spécifie la diff.

const domElement = document.getElementsByClassName("list")[0];

diffs.forEach((diff) => {

    const newElement = document.createElement(diff.newNode.tagName);
    /* Ajouter les attributs ... */
    
    if (diff.oldNode) {
        // Si version originale, la remplacer par la nouvelle version
        domElement.replaceChild(diff.newNode, diff.index);
    } else {
        // Sinon, créer un nouveau noeud
        domElement.appendChild(diff.newNode);
    }
})

Remarque : il s'agit là d'une version simplifiée du fonctionnement du DOM virtuel et il y a bien des cas que je n'aborde pas ici.

Le DOM virtuel et les frameworks

On travaille en réalité bien plus souvent avec le DOM virtuel via un framework.

Des frameworks comme React et Vue utilisent le concept du DOM virtuel pour améliorer les performances des mises à jour du DOM. Par exemple notre composant list peut être écrit ainsi en React :

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

Si nous voulions mettre à jour notre liste, nous pourrions juste réécrire le template liste et appeler ReactDOM.render() à nouveau, en lui passant la nouvelle liste.

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

Comme React utilise le DOM virtuel, même si nous opérons un rendu du template entier, seules les parties ayant changé seront mises à jour. Un coup d'oeil à notre DevTool au moment du changement nous montre les éléments spécifiques ainsi que les parties spécifiques qui changent.

DOM vs DOM virtuel

Pour conclure, le DOM virtuel est un outil nous offrant une interface plus aisée et plus performante avec les éléments du DOM. C'est un représentation objet JavaScript du DOM que nous pouvons modifier autant de fois que nous le voulons. Les modifications apportées à cet objet sont ensuite regroupées et les modifications apportées au DOM lui-même sont ciblées et effectuées moins souvent.

(publicité)


Intéressé par HTML ? Retrouvez une liste des meilleurs articles et ressources du web.

Tous les articles sur HTML parus dans la Cascade.


Articles sur les mêmes thèmes dans la Cascade :

Le DOM c'est quoi exactement, par Ire Aderinokun
Qu'est-ce que le DOM, par Chris Coyier
Optimiser votre front-end pour le navigateur, par Sanjay Purswani


original paru le 24 décembre 2018 dans Bits of Code
Traduit avec l’aimable autorisation de Bits of Code et de l’auteur.
Copyright Bits of Code © 2018
.

Sur l’auteur : est UI Designer et Front-End Developer à Lagos, au Nigéria. Son blog Bits of Code s’adresse aux développeurs autodidactes comme elle, elle y explore la théorie cachée derrière les morceaux de code que nous écrivons, afin de mieux comprendre le développement web. On peut la suivre sur Twitter ou sur Github.