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.
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
+1l'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);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"));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'écrancompte++;afficher(); // sans elle, rien ne bouge à l'écranTout 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>
);
}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 :setComptepré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 :
useStatele 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 changement | React 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ères | useState conserve l'état entre les rendus |
| Tu optimises les mises à jour DOM à la main | Le 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.