Soyez prudent lors de l'itération sur les tableaux !!
Il est faux de penser que l'utilisation de l'index de l'élément dans le tableau est un moyen acceptable de supprimer l'erreur que vous connaissez probablement:
Each child in an array should have a unique "key" prop.
Cependant, dans de nombreux cas, ce n'est pas le cas! Il s'agit d'un anti-modèle qui peut dans certaines situations conduire à un comportement indésirable .
Comprendre l' key
hélice
React utilise l' key
hélice pour comprendre la relation élément-élément DOM, qui est ensuite utilisée pour le processus de réconciliation . Il est donc très important que la clé reste toujours unique , sinon il y a de fortes chances que React mélange les éléments et mute le mauvais. Il est également important que ces touches restent statiques tout au long de tous les rendus afin de maintenir les meilleures performances.
Cela étant dit, il n'est pas toujours nécessaire d'appliquer ce qui précède, à condition que l'on sache que le tableau est complètement statique. Cependant, l'application des meilleures pratiques est encouragée dans la mesure du possible.
Un développeur React a déclaré dans ce numéro GitHub :
- la clé n'est pas vraiment une question de performance, c'est plus une question d'identité (qui à son tour conduit à de meilleures performances). les valeurs attribuées au hasard et changeantes ne sont pas une identité
- Nous ne pouvons pas fournir de clés de manière réaliste [automatiquement] sans savoir comment vos données sont modélisées. Je suggérerais peut-être d'utiliser une sorte de fonction de hachage si vous n'avez pas d'identifiant
- Nous avons déjà des clés internes lorsque nous utilisons des tableaux, mais ce sont les index du tableau. Lorsque vous insérez un nouvel élément, ces clés sont incorrectes.
En bref, un key
devrait être:
- Unique - Une clé ne peut pas être identique à celle d'un composant frère .
- Statique - Une clé ne doit jamais changer entre les rendus.
Utilisation de l' key
hélice
Selon l'explication ci-dessus, étudiez attentivement les exemples suivants et essayez de mettre en œuvre, si possible, l'approche recommandée.
Mauvais (potentiellement)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
C'est sans doute l'erreur la plus courante lors de l'itération sur un tableau dans React. Cette approche n'est pas techniquement "mauvaise" , c'est juste ... "dangereuse" si vous ne savez pas ce que vous faites. Si vous parcourez un tableau statique, c'est une approche parfaitement valide (par exemple un tableau de liens dans votre menu de navigation). Cependant, si vous ajoutez, supprimez, réorganisez ou filtrez des éléments, vous devez être prudent. Jetez un œil à cette explication détaillée dans la documentation officielle.
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Dans cet extrait, nous utilisons un tableau non statique et nous ne nous limitons pas à l'utiliser comme une pile. Il s'agit d'une approche dangereuse (vous comprendrez pourquoi). Notez que lorsque nous ajoutons des éléments au début du tableau (essentiellement non décalés), la valeur de chacun <input>
reste en place. Pourquoi? Parce que le key
n'identifie pas chaque élément de manière unique.
En d' autres termes, tout d' abord Item 1
a key={0}
. Lorsque nous ajoutons le deuxième élément, l'élément supérieur devient Item 2
, suivi Item 1
du deuxième élément. Cependant, maintenant Item 1
a key={1}
et non key={0}
plus. Au lieu de cela, a Item 2
maintenant key={0}
!!
En tant que tel, React pense que les <input>
éléments n'ont pas changé, car la Item
touche avec 0
est toujours en haut!
Alors pourquoi cette approche n'est- elle que parfois mauvaise?
Cette approche n'est risquée que si le tableau est en quelque sorte filtré, réorganisé ou si des éléments sont ajoutés / supprimés. S'il est toujours statique, il est parfaitement sûr à utiliser. Par exemple, un menu de navigation comme ["Home", "Products", "Contact us"]
peut être itéré en toute sécurité avec cette méthode, car vous n'aurez probablement jamais ajouter de nouveaux liens ou les réorganiser.
En bref, voici quand vous pouvez utiliser en toute sécurité l'index en tant que key
:
- Le tableau est statique et ne changera jamais.
- Le tableau n'est jamais filtré (affiche un sous-ensemble du tableau).
- Le tableau n'est jamais réorganisé.
- Le tableau est utilisé comme une pile ou LIFO (dernier entré, premier sorti). En d'autres termes, l'ajout ne peut être effectué qu'à la fin du tableau (c'est-à-dire push), et seul le dernier élément peut être supprimé (c'est-à-dire pop).
Si, à la place, dans l'extrait ci-dessus, nous avions poussé l'élément ajouté à la fin du tableau, l'ordre de chaque élément existant serait toujours correct.
Très mauvais
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Bien que cette approche garantisse probablement l'unicité des clés, elle obligera toujours à réagir pour restituer chaque élément de la liste, même lorsque cela n'est pas nécessaire. C'est une très mauvaise solution car elle a un impact considérable sur les performances. Sans oublier que l'on ne peut pas exclure la possibilité d'une collision de clés dans le cas où Math.random()
le même numéro serait produit deux fois.
Des clés instables (comme celles produites par Math.random()
) entraîneront la recréation inutile de nombreuses instances de composants et nœuds DOM, ce qui peut entraîner une dégradation des performances et une perte d'état dans les composants enfants.
très bien
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
C'est sans doute la meilleure approche car elle utilise une propriété unique pour chaque élément de l'ensemble de données. Par exemple, si rows
contient des données extraites d'une base de données, on pourrait utiliser la clé primaire de la table ( qui est généralement un numéro à incrémentation automatique ).
La meilleure façon de choisir une clé est d'utiliser une chaîne qui identifie de manière unique un élément de liste parmi ses frères et sœurs. Le plus souvent, vous utiliseriez des identifiants de vos données comme clés
Bien
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
C'est aussi une bonne approche. Si votre jeu de données ne contient aucune donnée garantissant l'unicité ( par exemple un tableau de nombres arbitraires ), il y a un risque de collision de clés. Dans de tels cas, il est préférable de générer manuellement un identifiant unique pour chaque élément de l'ensemble de données avant d'itérer dessus. De préférence lors du montage du composant ou lors de la réception de l'ensemble de données ( par exemple depuis props
ou depuis un appel d'API asynchrone ), afin de le faire une seule fois , et non à chaque fois que le composant effectue un nouveau rendu. Il existe déjà une poignée de bibliothèques qui peuvent vous fournir de telles clés. Voici un exemple: react-key-index .
key
propriété unique . Cela aidera ReactJS à trouver des références aux nœuds DOM appropriés et à mettre à jour uniquement le contenu à l'intérieur du balisage, mais pas à restituer la table / ligne entière.