Dans cette vidéo je vous propose de découvrir le nouveau compiler de React qui permet d'optimiser automatiquement le rendu des composants et qui promet de faire oublier les memo() et useMemo().
Sommaire
00:00 Introduction
00:43 Fonctionnement actuel
03:22 Démonstration du compiler
04:41 Comment ça marche
09:57 Bugs
11:53 Le poid
13:58 Conclusion
L'état actuel
Pour comprendre l'intérêt de ce nouveau compiler il est important de rappeler le Fonctionnement actuel de React. Lorsqu'un composant parent est rendu (suite à un changement d'état, par exemple), tous ses composants enfants sont automatiquement re-rendus. Pour éviter les rendus inutiles de composants (surtout s'ils contiennent du code "lourd"), on est obligé de mémoïser avec memo afin que son rendu soit fait que si une de ses propriété a changée. Si le composant reçoit une fonction il faudra alors la mémoïser aussi à l'aide d'un useCallback().
L'Inconvénient est qu'on est obligé en tant que développeur de réfléchir au fonctionnement de notre composant pour savoir si il est intéréssant ou non de le memoïser. Plus grave, il est parfois nécessaire de décomposer un composant pour limiter l'impact d'un changement d'état et éviter des rendus excessifs du composant parent.
Un nouveau compiler pour optimiser
C'est dans ces coniditions que le nouveau compiler intervient. Pour l'activer, il faut activer un plugin babel particulier. Par exemple dans le cas de vite :
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
});
Vous pouvez aussi l'activer directement lors de la création d'un nouveau projet, car il est désormais intégré aux templates proposés par défaut. Avec ce plugin activé, on remarque que le comportement de React change et que dans certaines conditions le composant enfant n'est plus rendu à chaque changement du composant parent.
Prenons cet exemple simple :
function Root () {
const [count, setCount] = useState(0)
const increment = () => {
setCount(v => v + 1)
}
return <div>
<p>Compteur {count}</p>
<Button onClick={increment}>Increment</Button>
</div>
}
function Button ({onClick, children}) {
console.log('Button render')
return <button onClick={onClick}>{children}</button>
}
Quand on incrémente le compteur, le code du composant Button n'est pas appelé. Le compiler a été capable de mémoïser le composant et le callback d'incrémentation.
Sous le Capot du Compiler
Pour comprendre le fonctionnement de ce compiler il faut observer le code JavaScript généré à partir de notre code JSX. Pour cela vous pouvez utiliser le React Compiler Playground
Voici un exemple de code généré par le compiler :
const $ = _c(6);
const [count, setCount] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setCount(_temp);
};
$[0] = t0;
} else {
t0 = $[0];
}
const increment = t0;
- La variable
$est un tableau qui de taille fixe remplit deSymbol.for("react.memo_cache_sentinel")qui sera persisté entre les rendus du composant - Lors du premier rendu, il créera la fonction et l'enregistrera à la première position du tableau
- Lors du rendu suivant, vu que la valeur n'est plus la valeur initiale, la fonction sera récupéré du premier rendu.
Prenons un autre exemple, un élément qui dépend d'un morceau d'état.
let t1;
if ($[1] !== count) {
t1 = <p>Compteur {count}</p>;
$[1] = count;
$[2] = t1;
} else {
t1 = $[2];
}
Comme pour le cas précédent il va sauvegarder la valeur obtenue en mémorisant aussi la valeur des dépendances utilisées. A chaque nouveau rendu il comparera la valeur de la dépendance et si elle a changé il relancera le code associé.
Conclusion
La promesse de ce nouveau compiler est intéréssante car l'utilisation de useMemo(), memo() et useCallback() a toujours était un point de friction important dans l'utilisation de React. En revanche, dans les faits j'ai des doutes sur l'approche automatique du compiler qui n'aura pas forcément la finesse de l'approche manuel.
Les Avantages
- Code Plus Propre : On se débarrasse des fonctions spécifique au fonctionnement de React.
- Optimisation sans effort supplémentaire côté développeur
Les Inconvénients
- Le poid : L'ajout de la logique conditionnelle dans le code généré augmente le poid de l'application générée.
- Comportement Imprévisible : Il est impératif de respecter des règles particulières pour que l'optimisation fonctionne sans casser le projet.
- Contrôle moins fin : Le compiler retire le contrôle des mains du développeur et peut parfois avoir des comportements inattendus par rapport à une approche manuelle.
De mon côté, je conseille d'adopter ce nouveau compiler pour des nouveaux projets pour l'optimisation qu'il offre. En revanche, pour un projet existant il faudra agir avec prudence car le compiler change le comportement du code en sortie ce qui peut introduire des bugs. Il est d'ailleurs possible de l'adopter progressivement à l'aide d'une annotation.