Fai attenzione quando esegui l'iterazione sugli array !!
È un'idea sbagliata comune che l'uso dell'indice dell'elemento nell'array sia un modo accettabile di sopprimere l'errore che probabilmente conoscete:
Each child in an array should have a unique "key" prop.
Tuttavia, in molti casi non lo è! Questo è anti-pattern che in alcune situazioni può portare a comportamenti indesiderati .
Capire l' key
elica
React utilizza il key
prop per comprendere la relazione tra componente e elemento DOM, che viene quindi utilizzata per il processo di riconciliazione . È quindi molto importante che la chiave rimanga sempre unica , altrimenti ci sono buone probabilità che React mescoli gli elementi e muti quello errato. È anche importante che queste chiavi rimangano statiche per tutti i rendering di nuovo al fine di mantenere le migliori prestazioni.
Questo detto, uno non sempre necessario applicare quanto sopra, a condizione che sia noto che la matrice è completamente statico. Tuttavia, l'applicazione delle migliori pratiche è incoraggiata quando possibile.
Uno sviluppatore di React ha dichiarato in questo numero di GitHub :
- la chiave non riguarda davvero le prestazioni, è più l'identità (che a sua volta porta a prestazioni migliori). i valori assegnati in modo casuale e cambiando non sono identità
- Non possiamo fornire realisticamente le chiavi [automaticamente] senza sapere come sono modellati i tuoi dati. Suggerirei forse di usare una sorta di funzione di hashing se non hai ID
- Abbiamo già chiavi interne quando utilizziamo gli array, ma sono l'indice dell'array. Quando si inserisce un nuovo elemento, quelle chiavi sono sbagliate.
In breve, key
dovrebbe essere:
- Unico : una chiave non può essere identica a quella di un componente di pari livello .
- Statico : una chiave non deve mai cambiare tra i rendering.
Usando l' key
elica
Come da spiegazione sopra, studia attentamente i seguenti esempi e cerca di implementare, quando possibile, l'approccio raccomandato.
Cattivo (potenzialmente)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Questo è probabilmente l'errore più comune visto durante l'iterazione su un array in React. Questo approccio non è tecnicamente "sbagliato" , è solo ... "pericoloso" se non sai cosa stai facendo. Se stai iterando attraverso un array statico, questo è un approccio perfettamente valido (ad esempio un array di collegamenti nel tuo menu di navigazione). Tuttavia, se stai aggiungendo, rimuovendo, riordinando o filtrando gli elementi, devi fare attenzione. Dai un'occhiata a questa spiegazione dettagliata nella documentazione ufficiale.
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>
In questo frammento stiamo usando un array non statico e non ci stiamo limitando a usarlo come stack. Questo è un approccio non sicuro (vedrai perché). Si noti come quando si aggiungono elementi all'inizio dell'array (sostanzialmente non spostabile), il valore per ciascuno <input>
rimane al suo posto. Perché? Perché il key
non identifica in modo univoco ogni elemento.
In altre parole, all'inizio Item 1
ha key={0}
. Quando aggiungiamo il secondo elemento, diventa l'elemento principale Item 2
, seguito da Item 1
come secondo elemento. Tuttavia, ora Item 1
ha key={1}
e non key={0}
più. Invece, Item 2
ora ha key={0}
!!
In quanto tale, React pensa che gli <input>
elementi non siano cambiati, perché la Item
chiave with 0
è sempre in alto!
Allora perché questo approccio è solo a volte cattivo?
Questo approccio è rischioso solo se l'array viene in qualche modo filtrato, riorganizzato o gli elementi vengono aggiunti / rimossi. Se è sempre statico, è perfettamente sicuro da usare. Ad esempio, un menu di navigazione come ["Home", "Products", "Contact us"]
può essere tranquillamente ripetuto con questo metodo perché probabilmente non aggiungerai mai nuovi collegamenti o riorganizzali.
In breve, ecco quando puoi tranquillamente utilizzare l'indice come key
:
- L'array è statico e non cambierà mai.
- L'array non viene mai filtrato (visualizza un sottoinsieme dell'array).
- L'array non viene mai riordinato.
- L'array viene utilizzato come stack o LIFO (last in, first out). In altre parole, l'aggiunta può essere effettuata solo alla fine dell'array (ad es. Push) e solo l'ultimo elemento può mai essere rimosso (ad es. Pop).
Se invece, nel frammento precedente, avessimo spinto l'elemento aggiunto alla fine dell'array, l'ordine per ogni elemento esistente sarebbe sempre corretto.
Molto brutto
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Sebbene questo approccio probabilmente garantirà l'unicità delle chiavi, forzerà sempre la reazione a eseguire nuovamente il rendering di ciascun elemento nell'elenco, anche quando ciò non è necessario. Questa è una pessima soluzione in quanto influisce notevolmente sulle prestazioni. Per non parlare del fatto che non si può escludere la possibilità di una collisione chiave nell'evento che Math.random()
produce lo stesso numero due volte.
Chiavi instabili (come quelle prodotte da Math.random()
) causeranno la ripetizione inutile di molte istanze di componenti e nodi DOM, il che può causare il degrado delle prestazioni e la perdita di stato nei componenti figlio.
Molto bene
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Questo è probabilmente l' approccio migliore perché utilizza una proprietà unica per ciascun elemento nel set di dati. Ad esempio, se rows
contiene dati recuperati da un database, è possibile utilizzare la chiave primaria della tabella ( che in genere è un numero a incremento automatico ).
Il modo migliore per scegliere una chiave è utilizzare una stringa che identifichi in modo univoco un elemento dell'elenco tra i suoi fratelli. Molto spesso useresti gli ID dei tuoi dati come chiavi
Buona
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
Anche questo è un buon approccio. Se il set di dati non contiene dati che garantiscono l'univocità ( ad esempio una matrice di numeri arbitrari ), esiste la possibilità di una collisione chiave. In questi casi, è meglio generare manualmente un identificatore univoco per ciascun elemento nel set di dati prima di iterare su di esso. Preferibilmente quando si monta il componente o quando si riceve il set di dati ( ad es. Da props
o da una chiamata API asincrona ), al fine di farlo solo una volta e non ogni volta che il componente esegue nuovamente il rendering. Esistono già alcune librerie che possono fornirti tali chiavi. Ecco un esempio: reagire-chiave-indice .
key
proprietà uniche . Aiuterà ReactJS a trovare riferimenti ai nodi DOM appropriati e ad aggiornare solo il contenuto all'interno del mark-up, ma non a eseguire nuovamente il rendering dell'intera tabella / riga.