Un compteur. Un bouton, un nombre qui monte. C’est le premier exercice de tous les cours de JavaScript, et il tient en six lignes.

const bouton = document.querySelector('#plus');
const affichage = document.querySelector('#compte');

bouton.addEventListener('click', () => {
  affichage.textContent = Number(affichage.textContent) + 1;
});

Ça marche. Et il y a déjà quelque chose de bancal dedans, qui ne se voit pas encore : le compte n’est stocké nulle part. Il est dans le texte affiché à l’écran. Pour savoir où on en est, le programme lit ce que voit l’utilisateur.

Tant qu’il n’y a qu’un endroit qui affiche le nombre, personne ne le remarque.

Le moment où ça casse

Ajoutons ce que n’importe quel énoncé de TP ajoute à l’étape suivante : un badge dans l’en-tête qui reprend le compte, et un bouton qui se désactive à 10.

bouton.addEventListener('click', () => {
  const n = Number(affichage.textContent) + 1;
  affichage.textContent = n;
  badge.textContent = n;
  if (n >= 10) bouton.disabled = true;
});

Puis un bouton de remise à zéro.

reset.addEventListener('click', () => {
  affichage.textContent = 0;
  badge.textContent = 0;
});

Le bug est là, et vous l’avez probablement vu : après une remise à zéro, le bouton reste désactivé. Il manque bouton.disabled = false.

Ce qui compte n’est pas l’oubli. C’est pourquoi il était prévisible.

Dans cette version, chaque gestionnaire d’événement doit connaître toutes les conséquences de ce qu’il fait. Deux événements et trois conséquences, ça fait six choses à ne pas rater. Ajoutez un troisième bouton et un quatrième affichage : douze. La quantité de code juste à écrire croît comme un produit, pas comme une somme — et personne ne tient un produit dans sa tête.

Le déplacement

La correction ne consiste pas à ajouter la ligne manquante. Elle consiste à faire en sorte qu’on ne puisse plus l’oublier.

On sort la vérité de l’écran et on la met dans une variable. Puis on écrit une seule fonction dont le travail est de redessiner l’écran à partir de cette variable.

let etat = { compte: 0 };

function rendu() {
  affichage.textContent = etat.compte;
  badge.textContent = etat.compte;
  bouton.disabled = etat.compte >= 10;
}

bouton.addEventListener('click', () => { etat.compte++; rendu(); });
reset.addEventListener('click', () => { etat.compte = 0; rendu(); });

rendu();

Relisez le gestionnaire de remise à zéro. Il ne peut plus oublier de réactiver le bouton — parce qu’il ne sait pas que le bouton existe. Il change un nombre, c’est tout. Savoir à quoi ressemble l’écran quand le compte vaut zéro est le travail d’une autre fonction, et d’une seule.

Six choses à ne pas rater sont devenues deux gestionnaires plus une fonction de rendu. Une somme, et non plus un produit.

C’est tout le cycle, et vous venez de l’écrire sans framework :

graph LR
    E[Événement
clic, saisie, réponse réseau] --> S[État
la seule vérité] S --> R[Rendu
l'écran, dérivé de l'état] R -.->|l'utilisateur agit| E class S ok class R neutre

Un événement ne touche jamais à l’écran. Il modifie l’état. L’écran est recalculé à partir de l’état. La flèche du retour est en pointillés parce qu’elle ne passe pas par votre code : c’est l’utilisateur qui clique.

Ce que les frameworks ajoutent — et ce n’est pas le cycle

Regardez la dernière version. Qu’est-ce qui y est encore pénible ?

Les appels à rendu(). Il y en a un par gestionnaire, plus un au démarrage, et le jour où vous en oubliez un, l’écran ment : l’état a changé, l’affichage non.

C’est précisément, et à peu près uniquement, ce que React, Vue et Svelte suppriment. Ils observent l’état et appellent le rendu pour vous. Leurs trois réponses à cette question — comment savoir que l’état a changé ? — sont d’ailleurs ce qui les distingue le plus :

  • React vous demande de le dire explicitement, en passant par une fonction dédiée (setState, useState) plutôt qu’en affectant la variable ;
  • Vue enveloppe votre objet d’état dans un mandataire qui intercepte les affectations ;
  • Svelte lit votre code à la compilation et insère les appels lui-même.

Trois stratégies, une seule question. Et aucune des trois ne change le cycle : elles automatisent la troisième flèche.

C’est la raison pour laquelle apprendre les trois n’est pas trois fois le travail. Ce qui se transfère — ce qui est un état, ce qui n’en est pas, ce que le rendu a le droit de faire — se transfère intégralement. Ce qui ne se transfère pas, c’est la syntaxe, et la syntaxe est ce qui s’apprend le plus vite.

Ce que le cycle coûte

Un article qui s’arrêterait ici vous laisserait avec une idée trop propre.

Tout redessiner à chaque changement est absurde. Notre rendu() réécrit trois propriétés ; une vraie page en a des milliers. C’est ce problème — pas le cycle — qui a produit le DOM virtuel de React, les signaux de Svelte et de Solid, et les listes à clés. Quand vous lirez ces pages de documentation, vous saurez à quelle question elles répondent : comment ne pas tout redessiner.

Et surtout : l’écran contient de l’état que vous n’y avez pas mis. Où est le curseur. Quel texte est sélectionné. Où en est le défilement. Ce qui est tapé dans un champ que vous ne contrôlez pas. À quelle seconde en est une vidéo.

Rien de tout cela n’est dans votre objet etat, et un rendu naïf le détruit. Le champ perd le focus au milieu d’une saisie, la liste remonte en haut, la vidéo repart à zéro. Les débutants attribuent ces bugs au framework ; ils viennent de la collision entre deux endroits qui croient chacun détenir la vérité.

C’est de là que sortent les champs « contrôlés » et « non contrôlés », les clés sur les listes, et la moitié des questions de forum. Pas d’une complication gratuite : d’un partage de responsabilité qui n’est jamais entièrement propre.

La question qui reste

Le cycle est partagé. Ce sur quoi les frameworks divergent réellement, une fois qu’on l’a compris, c’est où l’état a le droit d’habiter : dans le composant qui l’affiche, dans un magasin global, dans l’URL, sur le serveur.

Ce choix-là ne se rattrape pas par de la syntaxe, et il structurera votre projet longtemps après que vous aurez oublié quelle fonction s’appelait comment.

Pour l’exercice qui vous attend cette semaine, la question utile n’est donc pas « quel framework ». C’est : dans votre dernier TP, combien d’endroits différents détenaient un bout de la vérité ?