Dans cette vidéo je vous propose de décrypter le fonctionnement de React à travers la création d'une librairie similaire. L'objectif est de mieux comprendre le fonctionnement du virtual dom et les mécanique derrière la réactivité des composants.
Cet article fait suite à l'article publié par Rodrigo Pombo (quelques modifications ont été apportées au code pour corriger certains problèmes).
Les différentes étapes
Etape 1 : La fonction createElement
Lorsque vous utilisez la syntaxe JSX le code est transformé automatiquement par des outils comme babel. Donc lorsque l'on écrit :
return <div className="demo">Hello <strong>World</strong></div>
Le code est traduit en :
return React.createElement('div', {class: 'demo'},
'Hello',
React.createElement('strong', {}, 'World')
)
Cette méthode va convertir nos éléments en objet afin de pouvoir les traiter ensuite dans la fonction render()
Etape 2 : La fonction render
Lorsque l'on souhaite insérer un élément dans notre DOM on utilise la fonction render()
de ReactDOM
. Une première implémentation de cette fonction consiste à lire notre arbre en ajoutant les éléments au DOM de manière récursive.
Etape 3 : La méthode concurrente
Le problème de l'approche récursive développée lors de l'étape 2 est qu'elle bloque totalement le navigateur pendant le traitement de l'arbre. Dans le cas d'un arbre conséquent cela peut avoir une impact négatif sur l'expérience utilisateur car l'interface se retrouve complètement bloquée pendant le traitement.
Pour remédier à ce problème on utilise un planificateur qui permet de traiter notre arbre noeud par noeud tout en permettant à un process externe de s'interposer (le navigateur peut ainsi gérer d'autres opérations en même temps que la génération du DOM). L'utilisation d'un planificateur implique d'être capable de morceler le traitement de l'arbre sous forme d'unité de travail capable de déterminer l'opération suivante à effectuer.
Etape 4 : Fibers
Pour que le système fonctionne il faut déterminer une règle pour parcourir notre arbre. Le fonctionnement est le suivant
- Une fois un noeud traité, on traite le noeud enfant
- Si aucun noeud enfant n'existe, on traite le noeud voisin
- Si aucun noeud voisin n'existe, on remonte au voisin du parent
- Si le parent n'a pas de voisin, on continue de remonter au voisin du parent
Pour chaque noeud traité on va générer une propriété qui contiendra le DOM généré pour le noeud en question.
Etape 5 : Render et Commit
Afin d'optimiser les performances côté navigateur on n'ajoutera pas les éléments au DOM avant la fin du traitement de l'arbre. On ajoutera donc une phase de commit
qui parcourera notre arbre et qui liera l'ensemble des éléments du DOM via des appendChild
.
Etape 6 : Reconciliation
Jusqu'à maintenant le code n'est capable que de rajouter des éléments. On va donc mettre en place une étape de réconciliation qui va comparer les anciens noeuds au nouveaux afin de détecter les changements à effectuer (Ajout, Modification ou Suppression de l'élément).
Pour effectuer cette comparaison on sauvegardera l'ancien arbre "Fibers" et on le parcourera en parallèle lorsque l'on génèrera notre nouvel arbre.
- Si les 2 éléments ont le même type, alors on modifiera les attributs / évènements.
- Si les 2 éléments n'ont pas le même type alors on supprimera l'ancien et on ajoutera le nouveau.
- Si un élément est présent dans l'ancien arbre, mais pas dans le nouveau on le supprime.
- Inversement, si un élément n'existe pas dans l'ancien arbre, on l'ajoute simplement.
Etape 7 : Composant fonctionnel
La gestion d'un composant sous forme de fonction s'avère relativement aisé (par rapport au reste) car il suffit d'éxécuter la fonction pour obtenir sa structure. Il faudra faire en revanche des adaptations pour gérer des éléments qui n'auront pas nécessairement d'existance dans le DOM.
Etape 8 : Les hooks
Pour créer un hook on va créer une méthode useState()
qui permettra d'ajouter un hook à notre composant dans une propriété hooks
. Tout comme pour React l'ordre d'appel des hooks ne doit pas changer car l'index est récupéré comme référence losqu'un nouveau rendu est effectué.