¡Tenga cuidado al iterar sobre matrices!
Es un error común pensar que usar el índice del elemento en la matriz es una forma aceptable de suprimir el error con el que probablemente esté familiarizado:
Each child in an array should have a unique "key" prop.
Sin embargo, en muchos casos no lo es. Este es un anti-patrón que en algunas situaciones puede conducir a un comportamiento no deseado .
Entendiendo el key
prop
React usa el key
accesorio para comprender la relación componente-elemento DOM, que luego se usa para el proceso de reconciliación . Por eso es muy importante que la clave siempre sigue siendo único , de lo contrario hay una buena probabilidad de React será mezclar los elementos y mutar el incorrecto. También es importante que estas claves permanezcan estáticas durante todas las repeticiones para mantener el mejor rendimiento.
Dicho esto, no siempre es necesario aplicar lo anterior, siempre que se sepa que la matriz es completamente estática. Sin embargo, se recomienda aplicar las mejores prácticas siempre que sea posible.
Un desarrollador de React dijo en este número de GitHub :
- key is not really about performance, it's more about identity (which in turn leads to better performance). randomly assigned and changing values are not identity
- We can't realistically provide keys [automatically] without knowing how your data is modeled. I would suggest maybe using some sort of hashing function if you don't have ids
- We already have internal keys when we use arrays, but they are the index in the array. When you insert a new element, those keys are wrong.
En resumen, a key
debería ser:
- Único : una clave no puede ser idéntica a la de un componente hermano .
- Estático : una clave nunca debería cambiar entre renderizados.
Usando el key
accesorio
Según la explicación anterior, estudie cuidadosamente las siguientes muestras e intente implementar, cuando sea posible, el enfoque recomendado.
Malo (potencialmente)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Este es posiblemente el error más común que se observa al iterar sobre una matriz en React. Este enfoque no es técnicamente "incorrecto" , es simplemente ... "peligroso" si no sabe lo que está haciendo. Si está iterando a través de una matriz estática, este es un enfoque perfectamente válido (por ejemplo, una matriz de enlaces en su menú de navegación). Sin embargo, si agrega, elimina, reordena o filtra elementos, debe tener cuidado. Eche un vistazo a esta explicación detallada en la documentación oficial.
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>
En este fragmento, estamos usando una matriz no estática y no nos estamos restringiendo a usarla como una pila. Este es un enfoque inseguro (verá por qué). Tenga en cuenta que a medida que agregamos elementos al principio de la matriz (básicamente unshift), el valor de cada uno <input>
permanece en su lugar. ¿Por qué? Porque key
no identifica de forma única cada artículo.
En otras palabras, al principio Item 1
tiene key={0}
. Cuando agregamos el segundo elemento, el elemento superior se convierte en Item 2
, seguido Item 1
del segundo elemento. Sin embargo, ahora Item 1
tiene key={1}
y key={0}
ya no . En cambio, Item 2
ahora tiene key={0}
!!
Como tal, React cree que los <input>
elementos no han cambiado, ¡porque la Item
tecla with 0
está siempre en la parte superior!
Entonces, ¿por qué este enfoque solo a veces es malo?
Este enfoque solo es arriesgado si la matriz se filtra, reorganiza o se agregan / eliminan elementos de alguna manera. Si siempre es estático, entonces es perfectamente seguro de usar. Por ejemplo, un menú de navegación como ["Home", "Products", "Contact us"]
se puede iterar de forma segura con este método porque probablemente nunca agregará nuevos enlaces ni los reorganizará.
En resumen, aquí es cuando puede usar el índice de manera segura como key
:
- La matriz es estática y nunca cambiará.
- La matriz nunca se filtra (muestra un subconjunto de la matriz).
- La matriz nunca se reordena.
- La matriz se utiliza como una pila o LIFO (último en entrar, primero en salir). En otras palabras, solo se puede agregar al final de la matriz (es decir, presionar), y solo se puede eliminar el último elemento (es decir, pop).
Si en cambio, en el fragmento anterior, hubiéramos empujado el elemento agregado al final de la matriz, el orden de cada elemento existente siempre sería correcto.
Muy mal
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Si bien este enfoque probablemente garantizará la unicidad de las claves, siempre obligará a reaccionar para volver a representar cada elemento de la lista, incluso cuando esto no sea necesario. Esta es una muy mala solución ya que afecta enormemente el rendimiento. Sin mencionar que no se puede excluir la posibilidad de una colisión de claves en el caso de que se Math.random()
produzca el mismo número dos veces.
Unstable keys (like those produced by Math.random()
) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
Muy bien
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Este es posiblemente el mejor enfoque porque utiliza una propiedad que es única para cada elemento del conjunto de datos. Por ejemplo, si rows
contiene datos extraídos de una base de datos, se podría usar la clave principal de la tabla ( que normalmente es un número que se incrementa automáticamente ).
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys
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>
Este también es un buen enfoque. Si su conjunto de datos no contiene ningún dato que garantice la unicidad ( por ejemplo, una matriz de números arbitrarios ), existe la posibilidad de una colisión de claves. En tales casos, es mejor generar manualmente un identificador único para cada elemento en el conjunto de datos antes de iterar sobre él. Preferiblemente al montar el componente o cuando se recibe el conjunto de datos ( por ejemplo, desde props
o desde una llamada API asíncrona ), para hacer esto solo una vez , y no cada vez que el componente se vuelve a renderizar. Ya hay un puñado de bibliotecas que pueden proporcionarle dichas claves. Aquí hay un ejemplo: react-key-index .