¡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 antipatrón que en algunas situaciones puede conducir a un comportamiento no deseado .
Entendiendo el key
accesorio
React utiliza la key
utilería para comprender la relación del elemento componente al elemento DOM, que luego se utiliza para el proceso de reconciliación . Por lo tanto, es muy importante que la clave siempre permanezca única , de lo contrario, hay una buena posibilidad de que React mezcle los elementos y mute el incorrecto. También es importante que estas claves permanezcan estáticas en todos los renders 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 :
- La clave no se trata realmente de rendimiento, se trata más de identidad (que a su vez conduce a un mejor rendimiento). Los valores cambiantes y asignados al azar no son identidad
- Realmente no podemos proporcionar claves [automáticamente] sin saber cómo se modelan sus datos. Sugeriría tal vez usar algún tipo de función de hash si no tiene identificadores
- Ya tenemos claves internas cuando usamos matrices, pero son el índice en la matriz. Cuando inserta un nuevo elemento, esas claves son incorrectas.
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 debe cambiar entre los renders.
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 visto 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é). Observe cómo a medida que agregamos elementos al comienzo de la matriz (básicamente sin desplazamiento), el valor de cada uno <input>
permanece en su lugar. ¿Por qué? Porque el key
no identifica de forma única cada elemento.
En otras palabras, al principio Item 1
tiene key={0}
. Cuando agregamos el segundo elemento, el elemento superior se convierte Item 2
, seguido por Item 1
el 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 piensa que los <input>
elementos no han cambiado, ¡porque la Item
tecla with 0
siempre está 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. Si siempre es estático, entonces es perfectamente seguro de usar. Por ejemplo, un menú de navegación como ["Home", "Products", "Contact us"]
puede iterarse 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 usa como una pila o LIFO (última entrada, primera salida). En otras palabras, la adición solo se puede hacer al final de la matriz (es decir, push), y solo se puede eliminar el último elemento (es decir, pop).
Si, en cambio, en el fragmento anterior, empujáramos 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 a volver a representar cada elemento de la lista, incluso cuando no sea necesario. Esta es una solución muy mala, ya que afecta en gran medida el rendimiento. Sin mencionar que no se puede excluir la posibilidad de una colisión de teclas en el caso de que Math.random()
produzca el mismo número dos veces.
Las claves inestables (como las producidas por Math.random()
) provocarán que muchas instancias de componentes y nodos DOM se vuelvan a crear innecesariamente, lo que puede causar degradación del rendimiento y pérdida de estado en los componentes secundarios.
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 obtenidos de una base de datos, uno podría usar la Clave primaria de la tabla ( que generalmente es un número de incremento automático ).
La mejor manera de elegir una clave es usar una cadena que identifique de forma exclusiva un elemento de la lista entre sus hermanos. La mayoría de las veces usaría ID de sus datos como claves
Bueno
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 datos que garanticen la unicidad ( por ejemplo, una matriz de números arbitrarios ), existe la posibilidad de una colisión de teclas. En tales casos, es mejor generar manualmente un identificador único para cada elemento del 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 representar. Ya hay un puñado de bibliotecas que pueden proporcionarle esas claves. Aquí hay un ejemplo: react-key-index .
key
propiedad única . Ayudará a ReactJS a encontrar referencias a los nodos DOM apropiados y actualizar solo el contenido dentro del marcado, pero no volver a representar toda la tabla / fila.