Les key de Array.map(), comment ça marche ?
📌
Les key
, c’est ce qu’on doit renseigner lorsqu’on utilise map
pour render du DOM. On doit renseigner une valeur unique pour chaque élément, mais pourquoi ? Explications.
Le fonctionnement des key
Que se passe-t-il lorsque l’Array est mis à jour ?
Lorsque l’on met à jour le tableau (ex: via un setState
), React va comparer les différentes key fournies par le tableau d’avant/après la mise à jour.
- Si la key existe encore, React va uniquement re render le composant (maj)
- Si la key n’existe plus, elle sera démontée (supprimée)
- Si la key n’existait pas, elle est render (créée)
Exemple
Imaginons que nous trions le tableau d’asc à desc :
key 0 :
“rouge”→ key 0 : “bleu” (re render)key 1 :
“bleu”→ key 1 : “rouge” (re render)
On voit que les noms des keys restent les même : 0 et 1. Seules les valeurs attribuées à ces key changent ; elles sont donc mises à jour.
Pourquoi pas d’index dans key ?
On a vu dans l’exemple précédent que si l’on attribue au key l’index fournit par le map, chaque key n’est ni supprimée ni créée mais mise à jour.
Essayons maintenant avec un id unique :
key ‘RO’ : “rouge” → key ‘BL’ : “bleu” (rien ne se passe)
key ‘BL’ : “bleu” → key ‘RO’ : “rouge” (rien ne se passe)
Comme avec les index, il y a toujours les même noms de key, cependant les valeurs attribuées aux keys ‘RO’ et ‘BL’ sont toujours les mêmes, il n’y a donc aucune mise à jour à faire.
Pourquoi pas d’id dans key ?
Imaginons une pagination :
key ‘RO’ : “rouge”→ page 2 → key ‘VI’ : “violet” (render de 0)
key ‘BL’ : “bleu”→ page 2 → key ‘JA’ : “jaune” (render de 0)
Les id ont complètement changés puisque ce ne sont plus du tout les même éléments qui sont affichés (contrairement à un tri simple). Du coup, React va devoir démonter les keys supprimées + monter les nouvelles keys.
key 0 :
“rouge”→ page 2 → key 0 : “violet” (re render)key 1 :
“bleu”→ page 2 → key 1 : “jaune” (re render)
Ici il y a toujours les même keys, simplement plus les même valeurs attribuées : elles sont juste mises à jour.
⚠️
Si key avec index → le composant retourné dans le map ne doit pas être memo s’il contient des states : Comme il y a juste un re render et pas un mount du composant, le state n’est pas réinitialisé. Par exemple, le mail 1 sera sélectionné (key 0), on passe à la page 2, et celui qui aura la key 0, disons mail 21, sera sélectionné à sa place alors qu’il n’a rien à voir.
Exemple de tests pour un tri
Sans memo
→ Enfant re render quoi qu’il se passe avec la key puisque le parent re render
- Avec index :
const Key = () => {
let init = [
{ id: 'RE', name: 'red' },
{ id: 'BL', name: 'blue' },
];
const [colors, setColors] = useState(init);
const handleClick = () => {
const reversed = [...colors].reverse();
setColors(reversed);
};
return (
<div>
<button onClick={handleClick}>Click</button>
{colors.map((e, i) => (
<ItemMemo color={e} key={i} />
))}
</div>
);
};
export default Key;
const Item = ({ color }) => {
console.log('render');
useEffect(() => console.log('mount color'), [color]);
useEffect(() => console.log('mount'), []);
return <p>{color.name}</p>
);
};
// render x2
// mount color (le key 0)
// mount (le key 1)
// mount color (le key 0)
// mount (le key 1)
// ------ tri du tableau ------
// render x2
// mount color x2
On a plus les mêmes valeurs dans les keys (=props color associé à la key qui a changé). Donc comme state update → render → et on passe dans le useEffect de color
- Avec id :
...
<ItemMemo color={e} key={e.id} />
...
// pareil
// ------ tri du tableau ------
// render x2
La props color associée à la key n’a pas changé : on a uniquement un re render car le parent a re render (puisqu’on a pas de memo)
Avec memo
→ Si la props ne change pas, pas de re render enfant
- Avec index :
...
<ItemMemo color={e} key={i} />
...
const Item = memo({ color }) => { ... });
// pareil
// ------ tri du tableau ------
// render x2
// mount color x2
Toujours la même situation qu’avant. Comme la props a changé, le memo n’a aucun effet.
- Avec id :
...
<ItemMemo color={e} key={e.id} />
...
const Item = memo({ color }) => { ... });
// pareil
// ------ tri du tableau ------
// (rien)
Cette fois, comme la props n’a pas changé ET qu’il y a un memo sur le composant Item, il ne va pas re render.