Aller au contenu principal

Les fermetures

Une fonction qui se souvient : le secret qui se cache derrière useState.

Objectifs de la séance​

  • comprendre qu'une fonction garde l'accès aux variables de l'endroit où elle a été créée ;
  • écrire une fonction qui fabrique une autre fonction ;
  • fabriquer votre propre mini useState ;
  • reconnaître le piège de la valeur périmée (stale value).

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. dans labo-js/modules/, exécuter node main.js et lire la ligne affichée
  2. supprimer le mot export devant calculerPourcentage dans calculs.js, relancer node main.js, puis lire le message d'erreur
  3. dire quel type d'export (nommé ou par défaut) s'importe sans accolades
📌 Réponse attendue

Notions théoriques​

Une fonction peut se souvenir​

Observez ce code :

function creerCompteur() {
let valeur = 0; // variable locale à creerCompteur

return () => {
valeur = valeur + 1;
return valeur;
};
}

const compteur = creerCompteur();

console.log(compteur()); // 1
console.log(compteur()); // 2
console.log(compteur()); // 3

Étonnant : creerCompteur est terminée depuis longtemps, et pourtant la fonction renvoyée se souvient de valeur et la fait augmenter à chaque appel.

C'est une fermeture (closure) : une fonction garde un accès permanent aux variables de l'endroit où elle a été créée, même après la fin de la fonction parente.

Image à retenir : la fonction emporte avec elle un petit sac à dos contenant les variables dont elle a besoin.

Chaque appel crée un sac à dos différent​

const compteurA = creerCompteur();
const compteurB = creerCompteur();

console.log(compteurA()); // 1
console.log(compteurA()); // 2
console.log(compteurB()); // 1 (compteurB a sa propre variable valeur)

Les deux compteurs sont indépendants : chacun a sa propre variable valeur, invisible de l'extérieur. On ne peut pas la modifier par accident.

Votre propre mini useState​

Voici comment React mémorise une valeur entre deux affichages. Cette version simplifiée utilise une fermeture et une destructuration de tableau :

function creerEtat(valeurInitiale) {
let valeur = valeurInitiale;

const lire = () => valeur;
const ecrire = (nouvelleValeur) => {
valeur = nouvelleValeur;
};

return [lire, ecrire];
}

const [lireScore, ecrireScore] = creerEtat(0);

console.log(lireScore()); // 0
ecrireScore(5);
console.log(lireScore()); // 5

Le vrai useState(0) renvoie aussi un tableau de deux éléments : la valeur et une fonction pour la modifier. La mémoire de ce couple est entretenue par React grâce au même principe. Vous le découvrirez dans la dernière partie.

Le piège : la valeur périmée​

Une fonction qui reçoit une valeur en paramètre en garde une photo : si la variable d'origine change ensuite, la photo reste identique.

function creerAffichage(score) {
return () => console.log(`Score : ${score}`);
}

let scoreActuel = 0;
const afficher = creerAffichage(scoreActuel); // photo : 0

scoreActuel = 5;
afficher(); // Score : 0 (valeur périmée !)

Ce phénomène est la cause d'un bug classique en React : un callback créé lors d'un affichage garde la valeur de cet affichage-là. La solution (une mise à jour qui dépend de la valeur la plus récente) sera étudiée dans la partie 7.

Bonne pratique - Privilégier les fonctions pures

Les fermetures sont puissantes, mais une variable cachée est une variable qu'on ne voit pas. Utilisez-les quand c'est nécessaire (un état, un compteur, une minuterie) et gardez le reste du code en fonctions pures qui reçoivent tout par leurs paramètres.


Test de mémorisation/compréhension​


Qu'est-ce qu'une fermeture (closure) ?


Que s'affiche-t-il : const c = creerCompteur(); c(); console.log(c()); ?


Avec const a = creerCompteur(); const b = creerCompteur(); a(); a(); que renvoie b() ?


Que renvoie le vrai useState(0) en React, d'après le mini useState de la séance ?


Pourquoi afficher() affiche-t-il "Score : 0" alors que scoreActuel vaut 5 dans l'exemple de la valeur périmée ?



TP pour réfléchir et résoudre des problèmes​

Votre mission : fabriquer un compteur de score puis votre mini useState. Travaillez dans le fichier labo-js/fermetures.js.

Étape 1 — Un compteur de bonnes réponses​

Complétez creerCompteur : la fonction renvoyée doit ajouter 1 puis renvoyer la nouvelle valeur.


Bonne pratique - La variable cachée est protégée

Personne, hors de creerCompteur, ne peut écrire valeur = 1000. Seule la fonction renvoyée peut la modifier, et seulement en ajoutant 1 : le score ne peut pas être triché depuis l'extérieur.

Étape 2 — Deux compteurs indépendants​

Créez un deuxième compteur pour les mauvaises réponses et vérifiez qu'il est indépendant du premier.


Bonne pratique - Chaque appel, un état neuf

Appeler la fonction fabrique deux fois donne deux états séparés. Dans React, chaque composant affiché a de la même façon son propre état.

Étape 3 — Votre mini useState​

Complétez la fonction creerEtat pour qu'elle renvoie le couple [lire, ecrire].


Bonne pratique - Lire avec une fonction, pas avec une copie

Ici, lire est une fonction : elle regarde la valeur à l'instant de l'appel. Si on avait renvoyé directement valeur, on aurait récupéré une photo figée, c'est-à-dire le piège de la valeur périmée.

📌 Une solution

Ce qu'il faut retenir​

NotionRésumé
FermetureUne fonction garde l'accès aux variables de l'endroit où elle a été créée.
Fabrique de fonctionsUne fonction qui renvoie une fonction crée un état privé, propre à chaque appel.
Mini useStatecreerEtat renvoie [lire, ecrire] : le même principe que [valeur, setValeur] en React.
Valeur périméeUne valeur passée en paramètre est une photo : elle ne suit pas les changements ultérieurs.

Aperçu de la prochaine séance​

Que se passe-t-il quand une donnée est invalide, qu'un fichier est introuvable ou qu'un serveur ne répond pas ? Dans la prochaine séance, vous allez apprendre à lever et à attraper les erreurs avec throw et try/catch, pour que votre application reste debout.