Comprensione di chiavi univoche per i figli dell'array in React.js


657

Sto creando un componente React che accetta un'origine dati JSON e crea una tabella ordinabile.
Ciascuna delle righe di dati dinamici ha una chiave univoca assegnata ma sto ancora ricevendo un errore di:

Ogni bambino in un array dovrebbe avere un prop "chiave" univoco.
Controllare il metodo di rendering di TableComponent.

Il mio TableComponentmetodo di rendering restituisce:

<table>
  <thead key="thead">
    <TableHeader columns={columnNames}/>
  </thead>
  <tbody key="tbody">
    { rows }
  </tbody>
</table>

Il TableHeadercomponente è una singola riga e ha anche una chiave univoca assegnata ad esso.

Ogni rowin rowsè costruito da un componente con una chiave univoca:

<TableRowItem key={item.id} data={item} columns={columnNames}/>

E TableRowItemsembra così:

var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c) {
          return <td key={this.props.data[c]}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

Cosa sta causando l'errore univoco dell'elica chiave?


7
Le tue righe nell'array JS dovrebbero avere keyproprietà 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.
Kiril,

Puoi anche condividere rowsarray o più preferibilmente un jsfiddle? Non hai bisogno di una keyproprietà su theade tbodytra l'altro.
nilgun,

Ho aggiunto il componente riga alla domanda originale @nilgun.
Brett DeWoody,

3
È possibile che alcuni articoli non abbiano un ID o lo stesso ID?
nilgun,

Risposte:


623

È necessario aggiungere una chiave per ogni bambino e per ogni elemento all'interno dei bambini .

In questo modo React può gestire il minimo cambiamento di DOM.

Nel tuo codice, ognuno <TableRowItem key={item.id} data={item} columns={columnNames}/>sta cercando di rendere alcuni bambini al suo interno senza una chiave.

Guarda questo esempio .

Provare a rimuovere il key={i}dal <b></b>elemento all'interno del div del (e controllare la console).

Nell'esempio, se non forniamo una chiave per l' <b>elemento e vogliamo aggiornare solo l' elemento object.city, React deve eseguire nuovamente il rendering dell'intera riga rispetto al solo elemento.

Ecco il codice:

var data = [{name:'Jhon', age:28, city:'HO'},
            {name:'Onhj', age:82, city:'HN'},
            {name:'Nohj', age:41, city:'IT'}
           ];

var Hello = React.createClass({

    render: function() {

      var _data = this.props.info;
      console.log(_data);
      return(
        <div>
            {_data.map(function(object, i){
               return <div className={"row"} key={i}> 
                          {[ object.name ,
                             // remove the key
                             <b className="fosfo" key={i}> {object.city} </b> , 
                             object.age
                          ]}
                      </div>; 
             })}
        </div>
       );
    }
});

React.render(<Hello info={data} />, document.body);

La risposta pubblicata da @Chris in fondo è molto più dettagliata di questa risposta. Dai un'occhiata a https://stackoverflow.com/a/43892905/2325522

Reagire alla documentazione sull'importanza delle chiavi nella riconciliazione: chiavi


5
Sto incontrando lo stesso errore esatto. È stato risolto dopo la chat? In tal caso, puoi inviare un aggiornamento a questa domanda.
Deke,

1
La risposta funziona, Deke. Assicurati solo che il keyvalore dell'elica sia univoco per ciascun elemento e stai inserendo l' keyelica nel componente più vicino ai confini dell'array. Ad esempio, in React Native, per prima cosa stavo cercando di mettere l' keyelica sul <Text>componente. Tuttavia, ho dovuto metterlo al <View>componente di cui è genitore <Text>. se Array == [(Visualizza> Testo), (Visualizza> Testo)] devi metterlo su Visualizza. Non testo.
scaryguy,

240
Perché è così difficile per React generare chiavi univoche?
Assapora Lucic il


5
Questa è praticamente la parola ufficiale su una nota fatta nella chat di problemi collegata sopra: le chiavi riguardano l'identità di un membro di un set e la generazione automatica di chiavi per elementi che emergono da un iteratore arbitrario ha probabilmente implicazioni sulle prestazioni all'interno del React biblioteca.
stessi

524

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' keyelica

React utilizza il keyprop 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, keydovrebbe 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' keyelica

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.

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 keynon identifica in modo univoco ogni elemento.

In altre parole, all'inizio Item 1ha key={0}. Quando aggiungiamo il secondo elemento, diventa l'elemento principale Item 2, seguito da Item 1come secondo elemento. Tuttavia, ora Item 1ha key={1}e non key={0}più. Invece, Item 2ora ha key={0}!!

In quanto tale, React pensa che gli <input>elementi non siano cambiati, perché la Itemchiave 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 rowscontiene 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 propso 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 .


1
Nei documenti ufficiali , usano toString()per convertire in stringa invece di lasciare come numero. È importante ricordare questo?
skube,

1
@skube, no, puoi usare anche numeri interi key. Non sono sicuro del perché lo stiano convertendo.
Chris,

1
Immagino che tu possa usare numeri interi ma dovresti ? Secondo i loro documenti affermano che "... il modo migliore per scegliere una chiave è usare una stringa che identifichi in modo univoco ..." (enfasi sulla mia)
skube,

2
@skube, sì, è perfettamente accettabile. Come indicato negli esempi sopra, è possibile utilizzare l'indice dell'elemento dell'array iterato (e questo è un numero intero). Anche i documenti dichiarano: "Come ultima risorsa, puoi passare l'indice di un oggetto nella matrice come chiave" . Quello che succede è che comunque keyfinisce sempre per essere una stringa.
Chris,

3
@ farmcommand2, le chiavi vengono applicate a React Components e devono essere univoche tra i fratelli . Questo è indicato sopra. In altre parole, unico nell'array
Chris

8

Questo può aiutare o meno qualcuno, ma potrebbe essere un riferimento rapido. Questo è anche simile a tutte le risposte presentate sopra.

Ho molte posizioni che generano un elenco usando la struttura seguente:

return (
    {myList.map(item => (
       <>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </>
     )}
 )

Dopo un po 'di tentativi ed errori (e alcune frustrazioni), l'aggiunta di una proprietà chiave al blocco più esterno l'ha risolta. Si noti inoltre che il tag <> è ora sostituito con il tag ora.

return (

    {myList.map((item, index) => (
       <div key={index}>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </div>
     )}
 )

Naturalmente, ho usato ingenuamente l'indice iterante (indice) per popolare il valore chiave nell'esempio sopra. Idealmente, useresti qualcosa che è unico per la voce di elenco.


Questo è stato estremamente utile, grazie! Non mi ero nemmeno reso conto di doverlo inserire nello strato più esterno
Charles Smith il

6

Avvertenza: ogni figlio di un array o di un iteratore dovrebbe avere un puntello "chiave" univoco.

Questo è un avvertimento in quanto per gli elementi dell'array su cui andremo a scorrere sarà necessaria una somiglianza unica.

React gestisce l'iterazione del rendering dei componenti come array.

Il modo migliore per risolvere questo problema è fornire l'indice sugli elementi dell'array su cui si eseguirà l'iterazione, ad esempio:

class UsersState extends Component
    {
        state = {
            users: [
                {name:"shashank", age:20},
                {name:"vardan", age:30},
                {name:"somya", age:40}
            ]
        }
    render()
        {
            return(
                    <div>
                        {
                            this.state.users.map((user, index)=>{
                                return <UserState key={index} age={user.age}>{user.name}</UserState>
                            })
                        }
                    </div>
                )
        }

L'indice è puntelli incorporati di React.


2
Questo approccio è potenzialmente pericoloso se gli articoli vengono riorganizzati in qualche modo. Ma se rimangono statici, allora va bene.
Chris,

@chris Sono completamente d'accordo con te perché in questo caso l'indice può essere duplicato. Meglio usare valori dinamici per chiave.
Shashank Malviya,

@chris Sono anche d'accordo con il tuo commento. Dovremmo usare valori dinamici piuttosto che indicizzare perché potrebbero esserci dei duplicati. Per semplificare l'ho fatto. A proposito grazie per il tuo contributo (votato)
Shashank Malviya,

6

Aggiungi la chiave univoca ai tuoi componenti

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})

6

Controlla: chiave = undef !!!

Hai anche ricevuto il messaggio di avviso:

Each child in a list should have a unique "key" prop.

se il codice è completo, ma se attivo

<ObjectRow key={someValue} />

someValue non è definito !!! Si prega di controllare prima questo. Puoi risparmiare ore.


1

La migliore soluzione per definire la chiave univoca in reagire: all'interno della mappa hai inizializzato il post nome quindi la chiave definita da chiave = {post.id} o nel mio codice vedi che definisco l'elemento nome quindi definisco chiave per chiave = {item.id }:

<div className="container">
                {posts.map(item =>(

                    <div className="card border-primary mb-3" key={item.id}>
                        <div className="card-header">{item.name}</div>
                    <div className="card-body" >
                <h4 className="card-title">{item.username}</h4>
                <p className="card-text">{item.email}</p>
                    </div>
                  </div>
                ))}
            </div>


0

Questo è un avvertimento, ma affrontare questo renderà i Reati molto più veloci ,

Questo perché è Reactnecessario identificare in modo univoco ogni elemento nell'elenco. Diciamo se lo stato di un elemento di quell'elenco cambia in Reacts, Virtual DOMquindi React deve capire quale elemento è stato modificato e dove nel DOM deve cambiare in modo che il DOM del browser sia sincronizzato con il DOM virtuale di Reacts.

Come soluzione basta introdurre un keyattributo per ogni litag. Questo keydovrebbe essere un valore univoco per ogni elemento.


Questo non è del tutto corretto. Il rendering non sarà più veloce se aggiungi l' keyelica. Se non ne fornisci uno, React ne assegnerà uno automaticamente (l'indice corrente dell'iterazione).
Chris,

@Chris in quel caso perché genera un avvertimento?
prime

perché non fornendo una chiave, React non sa come modellare i tuoi dati. Ciò può portare a risultati indesiderati se l'array viene modificato.
Chris,

@Chris in quel caso di modifica dell'array, reagirà correttamente agli indici in base a ciò se non fornessimo le chiavi. In ogni caso, ho pensato che la rimozione di overhead extra da React avrebbe avuto un impatto sul processo di rendering.
prime

di nuovo, React fondamentalmente lo farà key={i}. Quindi dipende dai dati contenuti nell'array. Ad esempio, se hai la lista ["Volvo", "Tesla"], ovviamente la Volvo viene identificata dalla chiave 0e dalla Tesla con 1- perché questo è l'ordine in cui appariranno nel loop. Ora se riordini l'array i tasti vengono scambiati. Per React, poiché "oggetto" 0è ancora in cima, interpreterà piuttosto questa modifica come una "rinomina", piuttosto che un riordino. Le chiavi corrette qui dovrebbero essere, in ordine, 1quindi 0. Non riordinare sempre a metà del runtime, ma quando lo fai, è un rischio.
Chris,

0
var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c, i) {
          return <td key={i}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

Questo risolverà il problema.


0

Mi sono imbattuto in questo messaggio di errore perché è <></>stato restituito per alcuni elementi dell'array quando invece nulldeve essere restituito.


-1

Ho risolto questo problema usando Guid per ogni tasto in questo modo: Generating Guid:

guid() {
    return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' +
        this.s4() + '-' + this.s4() + this.s4() + this.s4();
}

s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}

E quindi assegnando questo valore ai marker:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}

2
L'aggiunta di un numero casuale come chiave è dannosa! Impedirà la reazione per rilevare le modifiche, che è lo scopo della chiave.
Pihentagy,

-1

Fondamentalmente non dovresti usare l'indice dell'array come chiave univoca. Piuttosto puoi usare a setTimeout(() => Date.now(),0)per impostare la chiave univoca o uuid univoco o qualsiasi altro modo che genererà un ID univoco ma non l'indice dell'array come ID univoco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.