18
Comprendre et utiliser useCallback - Tuto
Il y a quelques semaines, j’ai réaliser un test technique pour un poste de développeur react Junior. Le test proposait 2 exercices dont un très simple:
Réaliser un composant comprenant 5 checkboxes. 4 checkboxes normal, et 1 permettant de sélectionner ou de désélectionner tous les autres.
Point important précisé dans l’énoncé « Prenez votre temps ». Chose que je n’ai pas faite.
Je me suis précipité et me suis fait recaler pour la raison suivante: Code non performant !
Je vous propose donc dans cet article de voir, par un exemple très simple, comment améliorer ce type de composant avec les méthodes useCallback et memo proposées par React qui vont permettre d’éviter les rendus inutiles.
-
Mise en place des composants
a - On crée notre composant Checkboxe. Celui-ci reçoit des props. Un permettra de gérer son état checked, id pour matcher avec le label, et la fonction handleChange sur l’évènement onChange de l’input.
On n'oublie pas les PropTypes ;)
import React from 'react';
import PropTypes from 'prop-types';
const Checkboxe = ({
label,
checked,
handleChange,
id,
}) => {
console.log('id : ' + id);
return (
<div>
<input
type="checkbox"
id={id}
name={id}
checked={checked}
onChange={handleChange}
/>
<label htmlFor={id}>
{label}
</label>
</div>
);
}
Checkboxe.defaultProps = {
label: 'item 1',
id: 'scales',
checked: true,
handleChange: () => {},
array: [],
}
Checkboxe.propTypes = {
label: PropTypes.string,
id: PropTypes.string,
checked: PropTypes.bool,
handleChange: PropTypes.func,
array: PropTypes.array,
}
export default Checkboxe;
b - On crée notre composant parent, qui va gérer les états des checkboxes. On va appeler dans celui ci, 3 Checkboxes (pour faire très simple)
import React from 'react';
import Checkboxe from './Checkboxe';
const CheckForDev = () => {
return (
<div className="container">
<div className="checkboxes-container">
<Checkboxe
label="Item 1"
id="checkboxe1"
checked={}
handleChange={}
/>
<Checkboxe
label="Item 2"
id="checkboxe2"
checked={}
handleChange={}
/>
<Checkboxe
label="Item 3"
id="checkboxe3"
checked={}
handleChange={}
/>
</div>
</div>
);
}
export default CheckForDev;
c - On déclare un state pour chaque Checkboxe
const [check1, setCheck1] = useState(false);
const [check2, setCheck2] = useState(false);
const [check3, setCheck3] = useState(false);
d - On fait passer en props de chaque checkboxe son state ainsi sa fonction de changement d’état.
<Checkboxe
label="Item 1"
id="checkboxe1"
checked={check1}
handleChange={() => setCheck1(prev => !prev)}
/>
<Checkboxe
label="Item 2"
id="checkboxe2"
checked={check2}
handleChange={() => setCheck2(prev => !prev)}
/>
<Checkboxe
label="Item 3"
id="checkboxe3"
checked={check3}
handleChange={() => setCheck3(prev => !prev)}
/>
On peut maintenant jouir pleinement des checkboxes qui fonctionne.
C’est génial !!
C’est plus ou moins avec ce code que je me suis fait recaler du poste… ( Tu m'étonnes !!! )
Pourquoi ??
Pour répondre à cette question, dans le composant checkboxe loguons le props id pour bien voir quel composant est rendu.
console.log('id : ' + id);
Lors du premier rendu, quand l’app se monte, on peut voir en console 3 logs. Un pour chaque composant.
Quand on clique sur un Checkboxe, on voit que les 3 inputs sont re-rendus….
Or, il n'y a qu'une seul valeur qui a changé. Il y a donc 2 composants qui sont re-rendus inutilement.
En effet, une valeur d’état du composant gérant le state des checkboxes change, du coup c’est tout ce composant qui est re-rendu.
Pour des soucis de performances nous pouvons éviter cela, et permettre, dans notre exemple, de re-rendre seulement un checkboxe lors d’un changement d’état de ces derniers.
Comment ?
Grâce au méthodes useCallback et mémo de React.
useCallback va permettre de mémoïser les fonctions et de recréer une référence sur la stack seulement si nécessaire…
C’est parti !
2. Amélioration des composants avec les méthodes useCallback et memo
On crée une fonction pour chaque Checkboxe qui renverra une fonction de rappel mémoïsée. Celle-ci changera seulement si une des entrées change.
Ce qui veut dire par exemple que le Checkboxe numéro 1 ne sera re-rendu seulement si le state check1 change de valeur.
const handleCheck1 = useCallback(() => {
setCheck1(prev => !prev);
}, []);
const handleCheck2 = useCallback(() => {
setCheck2(prev => !prev);
}, []);
const handleCheck3 = useCallback(() => {
setCheck3(prev => !prev);
}, []);
Le props handleChange des composants CheckBoxe devient
handleChange={handleCheck1}
handleChange={handleCheck2}
handleChange={handleCheck3}
Testez.
Vous constatez en console que rien n'a changé.
Pour que cela fonctionne nous devons dire au composant checkboxe : «Mec, recrée une référence seulement si tu as un props qui change de valeur.»
Pour ca, on enveloppe Checkboxe avec React.memo de cette manière
const Checkboxe = React.memo(({
label,
checked,
handleChange,
id,
classes
}) => {
console.log('id : ' + id);
return (
<div className={classes} >
<input
type="checkbox"
id={id}
name={id}
checked={checked}
onChange={handleChange}
/>
<label htmlFor={id}>
{label}
</label>
</div>
);
})
On a bien 3 logs lors du montage de l’app.
Puis seulement le Checkboxe sur lequel on a cliqué qui est re-rendu.
Grâce à ce petit exemple, nous pouvons rapidement comprendre l’impact sur les performances d’une application plus conséquente.
J'ai mis ici [https://codesandbox.io/s/elegant-brown-cexzd?file=/src/styles.css] le composant sans et avec mémoïsation pour comparer les rendus facilement !
18