Articles
Oct 1, 2024

Comment fonctionne le hook useState ?

Comprendre le Fonctionnement Interne du Hook useState de React

Comment fonctionne le hook useState ?

Introduction

Le hook useState est une fonctionnalité clé de React qui permet de gérer l'état local dans les composants fonctionnels. Comprendre comment useState fonctionne sous le capot peut aider à mieux utiliser cette fonctionnalité et à optimiser les performances de vos applications React. Dans cet article, nous allons explorer les mécanismes internes de useState et voir comment React gère l'état des composants.

Initialisation de l'État

Lorsque vous appelez useState pour la première fois dans un composant, React initialise l'état avec la valeur que vous fournissez. Par exemple

const [count, setCount] = useState(0);

Ici, count est initialisé à 0.

Stockage de l'État

React utilise une structure de données interne appelée "fiber" pour stocker l'état de chaque composant. Chaque composant a une "fiber" associée qui contient des informations sur l'état, les props, et d'autres détails. Cette structure permet à React de gérer efficacement l'état et les rendus des composants.

Mise à Jour de l'État

Lorsque vous appelez la fonction de mise à jour de l'état (par exemple, setCount), React met à jour l'état dans la "fiber" du composant. Cette mise à jour peut déclencher un nouveau rendu du composant pour refléter les changements d'état.

Gestion des Rendus

React utilise un algorithme de réconciliation pour déterminer quelles parties de l'arbre des composants doivent être mises à jour. Lorsque l'état change, React compare l'arbre des composants actuel avec le nouvel arbre des composants et applique les différences au DOM de manière efficace.

Hooks et Ordre d'Appel

Les hooks, y compris useState, doivent toujours être appelés dans le même ordre à chaque rendu. React utilise cet ordre pour associer les états aux hooks corrects. Par exemple, si vous avez plusieurs appels à useState dans un composant, React se souvient de l'ordre et associe les états correctement.

Exemple de Code Interne Simplifié

Pour mieux comprendre comment useState pourrait fonctionner sous le capot, voici une version très simplifiée de ce que pourrait ressembler une implémentation interne de useState :

function useState(initialState) {
  if (!currentComponent) {
    throw new Error('useState must be called within a component');
  }

  const hook = currentComponent.hooks[currentHookIndex];

  if (!hook) {
    // Initial render
    const newHook = {
      state: initialState,
      queue: [],
    };
    currentComponent.hooks[currentHookIndex] = newHook;
    currentHookIndex++;
    return [newHook.state, (newState) => {
      newHook.queue.push(newState);
      scheduleUpdate();
    }];
  } else {
    // Update render
    if (hook.queue.length > 0) {
      hook.state = hook.queue.shift();
    }
    currentHookIndex++;
    return [hook.state, (newState) => {
      hook.queue.push(newState);
      scheduleUpdate();
    }];
  }
}

Variables Globales

let currentComponent = null;
let currentHookIndex = 0;
  • currentComponent : Cette variable garde une référence au composant actuellement en cours de rendu.
  • currentHookIndex : Cette variable garde une trace de l'index du hook actuellement en cours d'utilisation. Cela permet de s'assurer que les hooks sont appelés dans le même ordre à chaque rendu.

Fonction useState

function useState(initialState) {
  if (!currentComponent) {
    throw new Error('useState must be called within a component');
  }

  const hook = currentComponent.hooks[currentHookIndex];

  if (!hook) {
    // Initial render
    const newHook = {
      state: initialState,
      queue: [],
    };
    currentComponent.hooks[currentHookIndex] = newHook;
    currentHookIndex++;
    return [newHook.state, (newState) => {
      newHook.queue.push(newState);
      scheduleUpdate();
    }];
  } else {
    // Update render
    if (hook.queue.length > 0) {
      hook.state = hook.queue.shift();
    }
    currentHookIndex++;
    return [hook.state, (newState) => {
      hook.queue.push(newState);
      scheduleUpdate();
    }];
  }
}
  • useState(initialState) : Cette fonction prend un état initial et retourne un tableau avec l'état actuel et une fonction de mise à jour de l'état.
  • if (!currentComponent) : Vérifie si useState est appelé à l'intérieur d'un composant. Si ce n'est pas le cas, une erreur est levée.
  • const hook = currentComponent.hooks[currentHookIndex]; : Récupère le hook actuel à partir du composant actuel.
  • if (!hook) : Si le hook n'existe pas, cela signifie que c'est le premier rendu. Un nouvel objet hook est créé avec l'état initial et une file d'attente vide pour les mises à jour d'état.
  • currentComponent.hooks[currentHookIndex] = newHook; : Stocke le nouveau hook dans le tableau des hooks du composant.
  • currentHookIndex++; : Incrémente l'index du hook pour le prochain appel à useState.
  • return [newHook.state, (newState) => { ... }]; : Retourne l'état actuel et une fonction de mise à jour de l'état. La fonction de mise à jour ajoute la nouvelle valeur d'état à la file d'attente et planifie une mise à jour du composant.
  • if (hook.queue.length > 0) : Si la file d'attente contient des mises à jour d'état, la première mise à jour est appliquée.
  • return [hook.state, (newState) => { ... }]; : Retourne l'état actuel et une fonction de mise à jour de l'état.

Fonction scheduleUpdate

function scheduleUpdate() {  
	// Logic to schedule a component update
}
  • scheduleUpdate() : Cette fonction contient la logique pour planifier une mise à jour du composant. Dans une implémentation réelle, cela pourrait inclure la gestion des rendus asynchrones et la réconciliation du DOM.

Composant MyComponent

function MyComponent() {
  currentComponent = { hooks: [] };
  currentHookIndex = 0;

  const [count, setCount] = useState(0);

  return {
    render: () => `Count: ${count}`,
    setCount,
  };
}
  • currentComponent = { hooks: [] }; : Initialise le composant actuel avec un tableau vide de hooks.
  • currentHookIndex = 0; : Réinitialise l'index du hook pour le nouveau rendu.
  • const [count, setCount] = useState(0); : Utilise useState pour initialiser l'état count à 0 et obtenir la fonction de mise à jour setCount.
  • return { render: () => Count: ${count}, setCount }; : Retourne un objet avec une méthode render pour afficher l'état actuel et la fonction setCount pour mettre à jour l'état.

Utilisation du Composant

const component = MyComponent();
console.log(component.render()); // Output: Count: 0
component.setCount(1);
console.log(component.render()); // Output: Count: 1
  • const component = MyComponent(); : Crée une instance du composant.
  • console.log(component.render()); : Affiche l'état initial du composant.
  • component.setCount(1); : Met à jour l'état du composant.
  • console.log(component.render()); : Affiche l'état mis à jour du composant.

Conclusion

Ce code simplifié montre comment useState pourrait fonctionner sous le capot en utilisant des structures de données internes pour stocker et gérer l'état des composants. Les hooks doivent être appelés dans le même ordre à chaque rendu pour assurer une gestion correcte de l'état. La fonction de mise à jour de l'état planifie une mise à jour du composant, et la logique de rendu affiche l'état actuel.

En comprenant ces mécanismes internes, vous pouvez mieux utiliser useState et optimiser les performances de vos applications React.