Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesIntermédiaireSérie : JavaScript

JavaScript : Des composants à la main

Construire un composant réutilisable et réactif en JavaScript pur, puis voir concrètement, ligne à ligne, ce que React automatise pour toi.

Par Baptiste Vidal
5 min de lecture
Mis à jour il y a 2 semaines
javascriptjscomposantsdomreactstate

JavaScript : Des composants à la main

On dit souvent que React « invente » les composants. C'est faux : rien n'empêche de faire des composants en JavaScript pur. Un composant, c'est juste une brique d'interface réutilisable, avec son état et son affichage. On va en construire un vrai, à la main, avec ce qu'on a déjà vu (le DOM, les closures, les événements).

Ça marche très bien. Mais en le construisant, tu vas sentir pourquoi React existe.


# Ce qu'on veut construire

Un composant compteur, le plus classique qui soit :

  • il affiche un nombre ;
  • un bouton +1 l'incrémente ;
  • l'affichage se met à jour à chaque clic.

Trois exigences derrière ça, et ce sont exactement celles d'un vrai composant :

  • un état interne (le compte), qui lui appartient ;
  • un affichage qui reflète cet état ;
  • réutilisable : je dois pouvoir en poser deux sur la page sans qu'ils se marchent dessus.

# Version naïve : tout à la main

Première approche, directe et impérative. On crée les éléments, on branche le clic, on met à jour le texte :

const conteneur = document.querySelector("#compteur");
let compte = 0;
 
const affichage = document.createElement("span");
const bouton = document.createElement("button");
bouton.textContent = "+1";
affichage.textContent = `Compte : ${compte}`;
 
bouton.addEventListener("click", () => {
  compte++;
  affichage.textContent = `Compte : ${compte}`; // ← re-synchro à la main
});
 
conteneur.append(affichage, bouton);

Ça fonctionne. Mais deux choses coincent déjà :

  • l'état (compte) est une variable qui traîne dans la portée globale ;
  • après chaque changement, je dois penser à re-synchroniser l'affichage moi-même. J'oublie la ligne, et l'écran ment.

Et surtout : impossible d'en poser un deuxième sans tout dupliquer.


# Le rendre réutilisable : une factory + une closure

Pour en faire une vraie brique réutilisable, on l'enferme dans une fonction fabrique. L'état devient privé, gardé par la closure - chaque compteur a le sien :

function creerCompteur(conteneur) {
  let compte = 0; // état privé, capturé par la closure
 
  const affichage = document.createElement("span");
  const bouton = document.createElement("button");
  bouton.textContent = "+1";
 
  function afficher() {
    affichage.textContent = `Compte : ${compte}`;
  }
 
  bouton.addEventListener("click", () => {
    compte++;
    afficher(); // ← si tu oublies cet appel, l'affichage est faux
  });
 
  conteneur.append(affichage, bouton);
  afficher(); // rendu initial
}
 
creerCompteur(document.querySelector("#compteur-1"));
creerCompteur(document.querySelector("#compteur-2"));

C'est déjà un vrai composant : réutilisable, avec un état isolé. On a même isolé le rendu dans une fonction afficher(), séparée de la logique. On tient là le concept clé : d'un côté l'état, de l'autre une fonction qui le transforme en affichage.


# Le vrai problème : garder l'état et l'écran synchronisés

Regarde bien cette ligne :

compte++;
afficher(); // sans elle, rien ne bouge à l'écran

Tout le poids est là. C'est à toi d'appeler afficher() après chaque modification de l'état. Un compteur, ça va. Mais imagine une liste : à chaque ajout, il faut décider quoi reconstruire. Soit on rase et on recrée tout le DOM (innerHTML = ...) - simple, mais on perd le focus des champs, la position du scroll, et c'est lent ; soit on compare à la main l'ancien et le nouvel état pour ne toucher que ce qui change - précis, mais vite cauchemardesque.

Sur une vraie application, ce « penser à tout re-synchroniser au bon moment » est la source de bugs. Le code qui décrit quoi afficher se noie dans le code qui gère quand et comment mettre à jour le DOM.


# La même chose en React

Voici le compteur en React :

function Compteur() {
  const [compte, setCompte] = useState(0);
 
  return (
    <div>
      <span>Compte : {compte}</span>
      <button onClick={() => setCompte(compte + 1)}>+1</button>
    </div>
  );
}

Compare, ligne pour ligne, avec la version à la main :

  • Plus de createElement, plus d'append : tu décris le résultat en JSX.
  • Plus de fonction afficher() appelée à la main : setCompte prévient React que l'état a changé, et React relance la fonction puis met à jour le DOM - uniquement ce qui a changé.
  • L'état n'est plus une variable qui traîne : useState le conserve d'un rendu à l'autre pour toi.

Tu écris le quoi (« l'interface ressemble à ça pour ce compte »). React s'occupe du comment et du quand.


# Ce que React automatise vraiment

À la main (vanilla)Avec React
Tu appelles afficher() toi-même après chaque changementReact re-rend automatiquement quand l'état change
Tu manipules le DOM (createElement, textContent)Tu décris le JSX, React met le DOM à jour
L'état est une variable / une closure que tu gèresuseState conserve l'état entre les rendus
Tu optimises les mises à jour DOM à la mainLe Virtual DOM calcule le diff pour toi

React n'est donc pas magique : il automatise exactement la plomberie qu'on vient d'écrire à la main (état → rendu → mise à jour du DOM). C'est pour ça qu'un composant React est plus court et moins bugué : tu ne peux plus oublier le afficher().


# À retenir

  • On peut tout à fait faire des composants en JavaScript pur : une fonction fabrique + une closure pour l'état + une fonction de rendu suffisent.
  • Le point douloureux, ce n'est pas de créer le composant, c'est de garder l'affichage synchronisé avec l'état - à la main, tu dois y penser à chaque changement.
  • React automatise cette synchronisation : tu décris l'UI en fonction de l'état, il se charge de re-rendre et de mettre à jour le DOM.
  • Comprendre cette version manuelle, c'est comprendre ce que React fait pour toi - et donc mieux le débugger.

Prochaine étape : voir comment React formalise tout ça, avec React & Next.js : Introduction.