Les composants React ne fuient pas de mémoire parce qu'ils se rendent fréquemment. Ils fuient lorsque quelque chose en dehors de React continue de fonctionner après que le composant aurait dû disparaître. Les temporisateurs sont l'un des moyens les plus simples de créer ce bogue car setInterval et setTimeout vivent dans le système d'événements du navigateur, pas dans le cycle de vie des composants de React.
La fuite de temporisation classique
Cela semble inoffensif :
useEffect(() => {
const id = setInterval(() => {
refreshData();
}, 1000);
}, []);
Si le composant se démonte, l'intervalle continue de se déclencher. Cela signifie que le rappel, l'état capturé et tout travail déclenché par refreshData() peuvent rester actifs plus longtemps que prévu.
La solution est simple :
useEffect(() => {
const id = setInterval(() => {
refreshData();
}, 1000);
return () => clearInterval(id);
}, []);
Là où ça devient pire
Les fuites de temporisation deviennent plus coûteuses lorsque le rappel commence également un travail réseau :
useEffect(() => {
const id = setInterval(() => {
void refreshData();
}, 5000);
return () => clearInterval(id);
}, [refreshData]);
Maintenant, le bogue concerne non seulement la mémoire. Il peut entraîner un sondage dupliqué, un CPU gaspillé et des requêtes qui continuent d'être envoyées après que l'utilisateur ait navigué ailleurs.
Meilleure règle d'ingénierie
Tout effet qui acquiert une ressource de longue durée devrait la libérer dans le même effet :
- intervalles
- délais
- écouteurs d'événements
- observateurs
- abonnements
Cette règle est simple, mais elle attrape un grand pourcentage des bogues de fuite dans React. Si une ressource survit au rendu, elle devrait avoir un chemin de nettoyage explicite.
Lectures supplémentaires