Grundlegendes zu eindeutigen Schlüsseln für untergeordnete Array-Elemente in React.js

652

Ich erstelle eine React-Komponente, die eine JSON-Datenquelle akzeptiert und eine sortierbare Tabelle erstellt.
Jeder der dynamischen Datenzeilen ist ein eindeutiger Schlüssel zugewiesen, aber ich erhalte immer noch die Fehlermeldung:

Jedes Kind in einem Array sollte eine eindeutige "Schlüssel" -Stütze haben.
Überprüfen Sie die Rendermethode von TableComponent.

Meine TableComponentRendermethode gibt Folgendes zurück:

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

Das TableHeader Komponente ist eine einzelne Zeile und hat auch einen eindeutigen Schlüssel.

Jedes rowIn rowswird aus einer Komponente mit einem eindeutigen Schlüssel erstellt:

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

Und das TableRowItemsieht so aus:

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>
    )
  }
});

Was verursacht den eindeutigen Schlüssel-Prop-Fehler?

Brett DeWoody
quelle
7
Ihre Zeilen im JS-Array sollten eine eindeutige keyEigenschaft haben. Es wird ReactJS helfen, Verweise auf die entsprechenden DOM-Knoten zu finden und nur den Inhalt innerhalb des Markups zu aktualisieren, aber nicht die gesamte Tabelle / Zeile neu zu rendern.
Kiril
Können Sie auch ein rowsArray oder vorzugsweise eine jsfiddle teilen? Sie brauchen keine Notwendigkeit keyEigenschaft auf theadund tbodydurch die Art und Weise.
Nilgun
Ich habe die Zeilenkomponente zur ursprünglichen Frage @nilgun hinzugefügt.
Brett DeWoody
3
Ist es möglich, dass einige Elemente keine oder dieselbe ID haben?
Nilgun

Antworten:

619

Sie sollten jedem untergeordneten Element sowie jedem Element in untergeordneten Elementen einen Schlüssel hinzufügen .

Auf diese Weise kann React die minimale DOM-Änderung verarbeiten.

In Ihrem Code versucht jeder <TableRowItem key={item.id} data={item} columns={columnNames}/>, einige Kinder ohne Schlüssel in ihnen zu rendern.

Überprüfen Sie dieses Beispiel .

Versuchen Sie, das key={i}aus dem <b></b>Element in den Divs zu entfernen (und überprüfen Sie die Konsole).

Wenn wir im Beispiel dem <b>Element keinen Schlüssel geben und nur den aktualisieren möchten object.city, muss React die gesamte Zeile gegenüber nur dem Element neu rendern.

Hier ist der Code:

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);

Die Antwort von @Chris unten geht viel detaillierter als diese Antwort. Bitte werfen Sie einen Blick auf https://stackoverflow.com/a/43892905/2325522

Reagieren Sie auf die Dokumentation zur Bedeutung von Schlüsseln bei der Abstimmung: Schlüssel

jmingov
quelle
5
Ich habe genau den gleichen Fehler. Wurde dies nach dem Chat behoben? Wenn ja, können Sie bitte ein Update zu dieser Frage veröffentlichen.
Deke
1
Die Antwort funktioniert, Deke. Stellen Sie einfach sicher, dass der keyWert für die Requisite für jedes Element eindeutig ist, und platzieren Sie die keyRequisite an der Komponente, die den Arraygrenzen am nächsten liegt. In React Native habe ich beispielsweise zuerst versucht, keyRequisiten in <Text>Komponenten zu integrieren. Allerdings musste ich es auf eine <View>Komponente setzen, die übergeordnet ist <Text>. Wenn Array == [(Ansicht> Text), (Ansicht> Text)], müssen Sie es in Ansicht setzen. Kein Text.
Scaryguy
236
Warum ist es für React so schwierig, selbst eindeutige Schlüssel zu generieren?
Davor Lucic
13
@DavorLucic, hier ist eine Diskussion: github.com/facebook/react/issues/1342#issuecomment-39230939
Koddo
5
Dies ist so ziemlich das offizielle Wort in einer Notiz, die im oben verlinkten Issue-Chat gemacht wurde: Schlüssel beziehen sich auf die Identität eines Mitglieds eines Satzes, und die automatische Generierung von Schlüsseln für Elemente, die aus einem beliebigen Iterator hervorgehen, hat wahrscheinlich Auswirkungen auf die Leistung innerhalb der Reaktion Bibliothek.
Sameers
521

Seien Sie vorsichtig, wenn Sie über Arrays iterieren !!

Es ist ein weit verbreitetes Missverständnis, dass die Verwendung des Index des Elements im Array eine akzeptable Methode ist, um den Fehler zu unterdrücken, mit dem Sie wahrscheinlich vertraut sind:

Each child in an array should have a unique "key" prop.

In vielen Fällen ist dies jedoch nicht der Fall! Dies ist ein Anti-Muster , das in einigen Situationen zu unerwünschtem Verhalten führen kann .


Die keyRequisite verstehen

React verwendet die keyRequisite, um die Beziehung zwischen Komponente und DOM-Element zu verstehen, die dann für den Abstimmungsprozess verwendet wird . Es ist daher sehr wichtig, dass der Schlüssel immer eindeutig bleibt , da sonst die Wahrscheinlichkeit groß ist, dass React die Elemente verwechselt und das falsche mutiert. Es ist auch wichtig, dass diese Schlüssel bei allen erneuten Rendern statisch bleiben , um die beste Leistung zu erzielen.

Davon abgesehen muss man das oben Gesagte nicht immer anwenden, vorausgesetzt, es ist bekannt, dass das Array vollständig statisch ist. Die Anwendung von Best Practices wird jedoch nach Möglichkeit empfohlen.

Ein React-Entwickler sagte in dieser GitHub-Ausgabe :

  • Bei key geht es nicht wirklich um Leistung, sondern eher um Identität (was wiederum zu einer besseren Leistung führt). zufällig zugewiesene und sich ändernde Werte sind keine Identität
  • Wir können Schlüssel nicht [automatisch] realistisch bereitstellen, ohne zu wissen, wie Ihre Daten modelliert werden. Ich würde vorschlagen, vielleicht eine Art Hashing-Funktion zu verwenden, wenn Sie keine IDs haben
  • Wir haben bereits interne Schlüssel, wenn wir Arrays verwenden, aber sie sind der Index im Array. Wenn Sie ein neues Element einfügen, sind diese Schlüssel falsch.

Kurz gesagt, a keysollte sein:

  • Einzigartig - Ein Schlüssel kann nicht mit dem einer Geschwisterkomponente identisch sein .
  • Statisch - Ein Schlüssel sollte niemals zwischen Renderings wechseln.


Mit der keyStütze

Studieren Sie gemäß der obigen Erläuterung die folgenden Beispiele sorgfältig und versuchen Sie, den empfohlenen Ansatz nach Möglichkeit umzusetzen.


Schlecht (möglicherweise)

<tbody>
    {rows.map((row, i) => {
        return <ObjectRow key={i} />;
    })}
</tbody>

Dies ist wohl der häufigste Fehler, der beim Durchlaufen eines Arrays in React auftritt. Dieser Ansatz ist technisch nicht "falsch" , sondern nur ... "gefährlich", wenn Sie nicht wissen, was Sie tun. Wenn Sie ein statisches Array durchlaufen, ist dies ein absolut gültiger Ansatz (z. B. ein Array von Links in Ihrem Navigationsmenü). Wenn Sie jedoch Elemente hinzufügen, entfernen, neu anordnen oder filtern, müssen Sie vorsichtig sein. Schauen Sie sich diese ausführliche Erklärung in der offiziellen Dokumentation an.

In diesem Snippet verwenden wir ein nicht statisches Array und beschränken uns nicht darauf, es als Stapel zu verwenden. Dies ist ein unsicherer Ansatz (Sie werden sehen warum). Beachten Sie, dass beim Hinzufügen von Elementen am Anfang des Arrays (im Grunde genommen die Verschiebung) der Wert für jedes Element erhalten <input>bleibt. Warum? Weil das keynicht jedes Element eindeutig identifiziert.

Mit anderen Worten, zunächst Item 1hat key={0}. Wenn wir das zweite Element hinzufügen, wird das oberste Element Item 2gefolgt von Item 1dem zweiten Element. Doch jetzt Item 1hat key={1}und nicht key={0}mehr. Stattdessen hat Item 2jetzt key={0}!!

Aus diesem <input>Grund glaubt React, dass sich die Elemente nicht geändert haben, da der ItemWith-Schlüssel 0immer oben steht!

Warum ist dieser Ansatz nur manchmal schlecht?

Dieser Ansatz ist nur dann riskant, wenn das Array irgendwie gefiltert, neu angeordnet oder Elemente hinzugefügt / entfernt werden. Wenn es immer statisch ist, ist es absolut sicher zu bedienen. Zum Beispiel kann ein Navigationsmenü wie ["Home", "Products", "Contact us"]mit dieser Methode sicher durchlaufen werden, da Sie wahrscheinlich nie neue Links hinzufügen oder neu anordnen werden.

Kurz gesagt, hier ist, wann Sie den Index sicher verwenden können als key:

  • Das Array ist statisch und wird sich nie ändern.
  • Das Array wird niemals gefiltert (eine Teilmenge des Arrays anzeigen).
  • Das Array wird nie neu angeordnet.
  • Das Array wird als Stack oder LIFO verwendet (last in, first out). Mit anderen Worten, das Hinzufügen kann nur am Ende des Arrays erfolgen (dh Push), und nur das letzte Element kann jemals entfernt werden (dh Pop).

Hätten wir stattdessen in dem Snippet oben, schoben das zusätzliche Element am Ende des Arrays, die Reihenfolge für jedes vorhandenes Element würde immer richtig sein.


Sehr schlecht

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={Math.random()} />;
    })}
</tbody>

Während dieser Ansatz wahrscheinlich die Eindeutigkeit der Schlüssel garantiert, erzwingt er immer eine Reaktion, um jedes Element in der Liste neu zu rendern, selbst wenn dies nicht erforderlich ist. Dies ist eine sehr schlechte Lösung, da sie die Leistung stark beeinträchtigt. Ganz zu schweigen davon, dass man die Möglichkeit einer Schlüsselkollision nicht ausschließen kann, wenn Math.random()dieselbe Zahl zweimal erzeugt wird.

Instabile Schlüssel (wie die von Math.random()) führen dazu, dass viele Komponenteninstanzen und DOM-Knoten unnötig neu erstellt werden, was zu Leistungseinbußen und Statusverlust bei untergeordneten Komponenten führen kann.


Sehr gut

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uniqueId} />;
    })}
</tbody>

Dies ist wohl der beste Ansatz, da eine Eigenschaft verwendet wird, die für jedes Element im Dataset eindeutig ist. Wenn beispielsweise rowsDaten enthalten sind, die aus einer Datenbank abgerufen wurden, kann der Primärschlüssel der Tabelle verwendet werden ( normalerweise eine automatisch inkrementierende Zahl ).

Der beste Weg, einen Schlüssel auszuwählen, besteht darin, eine Zeichenfolge zu verwenden, die ein Listenelement unter seinen Geschwistern eindeutig identifiziert. Am häufigsten verwenden Sie IDs aus Ihren Daten als Schlüssel


Gut

componentWillMount() {
  let rows = this.props.rows.map(item => { 
    return {uid: SomeLibrary.generateUniqueID(), value: item};
  });
}

...

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uid} />;
    })}
</tbody>

Dies ist auch ein guter Ansatz. Wenn Ihr Datensatz keine Daten enthält, die die Eindeutigkeit garantieren ( z. B. ein Array beliebiger Zahlen ), besteht die Möglichkeit einer Schlüsselkollision. In solchen Fällen ist es am besten, manuell eine eindeutige Kennung für jedes Element im Dataset zu generieren, bevor Sie darüber iterieren. Vorzugsweise beim Mounten der Komponente oder beim Empfangen des Datasets ( z. B. von propsoder von einem asynchronen API-Aufruf ), um dies nur einmal und nicht jedes Mal zu tun , wenn die Komponente erneut gerendert wird. Es gibt bereits eine Handvoll Bibliotheken, die Ihnen solche Schlüssel zur Verfügung stellen können. Hier ist ein Beispiel: React-Key-Index .

Chris
quelle
1
In den offiziellen Dokumenten werden sie verwendet toString(), um in Zeichenfolgen zu konvertieren, anstatt sie als Nummer zu belassen. Ist es wichtig, sich daran zu erinnern?
Skube
1
@skube, nein, du kannst auch ganze Zahlen verwenden key. Ich bin mir nicht sicher, warum sie es konvertieren.
Chris
1
Ich denke, Sie können ganze Zahlen verwenden, aber sollten Sie? In ihren Dokumenten heißt es: "... der beste Weg, einen Schlüssel auszuwählen, ist die Verwendung einer Zeichenfolge , die eindeutig identifiziert ..." (Hervorhebung von mir)
Skube
2
@skube, ja das ist durchaus akzeptabel. Wie in den obigen Beispielen angegeben, können Sie den Elementindex des iterierten Arrays verwenden (und das ist eine Ganzzahl). Auch der docs Zustand: „Als letzten Ausweg, können Sie Artikel-Index im Array als Schlüssel übergeben“ . Was jedoch passiert ist, dass das keysowieso immer ein String ist.
Chris
3
@ farmcommand2, Schlüssel werden auf React Components angewendet und müssen unter Geschwistern eindeutig sein . Dies ist oben angegeben. Mit anderen Worten, einzigartig im Array
Chris
8

Dies kann jemandem helfen oder nicht, aber es kann eine Kurzreferenz sein. Dies ähnelt auch allen oben dargestellten Antworten.

Ich habe viele Standorte, die eine Liste mit der folgenden Struktur erstellen:

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

Nach ein wenig Versuch und Irrtum (und einigen Frustrationen) wurde es durch Hinzufügen einer Schlüsseleigenschaft zum äußersten Block behoben. Beachten Sie auch, dass das <> -Tag jetzt durch das Tag ersetzt wird.

return (

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

Natürlich habe ich den iterierenden Index (Index) naiv verwendet, um den Schlüsselwert im obigen Beispiel zu füllen. Idealerweise verwenden Sie etwas, das für das Listenelement eindeutig ist.

apil.tamang
quelle
Das war sehr hilfreich, danke! Ich wusste nicht einmal, dass ich es in die äußerste Schicht legen musste
Charles Smith
6

Warnung: Jedes Kind in einem Array oder Iterator sollte eine eindeutige "Schlüssel" -Stütze haben.

Dies ist eine Warnung, da für Array-Elemente, über die wir iterieren werden, eine eindeutige Ähnlichkeit erforderlich ist.

React behandelt das iterierende Rendern von Komponenten als Arrays.

Eine bessere Möglichkeit, dies zu beheben, besteht darin, einen Index für die Array-Elemente bereitzustellen, über die Sie iterieren möchten. Beispiel:

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>
                )
        }

Index ist React eingebaute Requisiten.

Shashank Malviya
quelle
2
Dieser Ansatz ist möglicherweise gefährlich, wenn die Elemente irgendwie neu angeordnet werden. Aber wenn sie statisch bleiben, ist das in Ordnung.
Chris
@chris Ich stimme Ihnen voll und ganz zu, da in diesem Fall der Index möglicherweise dupliziert wird. Verwenden Sie besser dynamische Werte für den Schlüssel.
Shashank Malviya
@chris Ich stimme auch Ihrem Kommentar zu. Wir sollten dynamische Werte anstelle von Index verwenden, da möglicherweise Duplikate vorhanden sind. Um es einfach zu machen, habe ich das getan. Übrigens danke für Ihren Beitrag (positiv bewertet)
Shashank Malviya
6

Fügen Sie einfach den eindeutigen Schlüssel zu Ihren Komponenten hinzu

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

Check: key = undef !!!

Sie haben auch die Warnmeldung erhalten:

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

Wenn Ihr Code vollständig ist, aber wenn aktiviert

<ObjectRow key={someValue} />

someValue ist undefiniert !!! Bitte überprüfen Sie dies zuerst. Sie können Stunden sparen.

Gerd
quelle
1

Beste Lösung, um einen eindeutigen Schlüssel für die Reaktion zu definieren: Innerhalb der Karte haben Sie den Namen post initialisiert und dann den Schlüssel durch key = {post.id} definiert. In meinem Code sehen Sie, dass ich das Namenselement definiere und dann den Schlüssel durch key = {item.id definiere }:

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

Khadim Rana
quelle
0

Dies ist eine Warnung, aber wenn Sie dies ansprechen, werden die Reaktionen viel schneller , .

Dies liegt daran React, dass jedes Element in der Liste eindeutig identifiziert werden muss. Nehmen wir an, ob sich der Status eines Elements dieser Liste in Reacts ändertVirtual DOM muss React herausfinden, welches Element geändert wurde und wo im DOM es geändert werden muss, damit das Browser-DOM mit dem virtuellen Reacts-DOM synchronisiert wird.

Als Lösung fügen Sie einfach keyjedem liTag ein Attribut hinzu . Dies keysollte für jedes Element ein eindeutiger Wert sein.

Prime
quelle
Das ist nicht ganz richtig. Das Rendern wird nicht schneller, wenn Sie die keyRequisite hinzufügen . Wenn Sie keine angeben, weist React automatisch eine zu (den aktuellen Index der Iteration).
Chris
@ Chris in diesem Fall, warum es eine Warnung auslöst?
Prime
Da React keinen Schlüssel angibt, weiß es nicht, wie Ihre Daten modelliert werden. Dies kann zu unerwünschten Ergebnissen führen, wenn das Array geändert wird.
Chris
@Chris korrigiert in diesem Fall der Array-Änderung die Indizes entsprechend, wenn wir keine Schlüssel angegeben haben. Wie auch immer, ich dachte, das Entfernen von zusätzlichem Overhead aus React wird sich auf den Renderprozess auswirken.
Prime
wieder wird React im Grunde tun key={i}. Es hängt also von den Daten ab, die Ihr Array enthält. Wenn Sie zum Beispiel die Liste haben ["Volvo", "Tesla"], wird der Volvo offensichtlich durch den Schlüssel und den Tesla mit identifiziert - denn dies ist die Reihenfolge, in der sie in der Schleife erscheinen. Wenn Sie nun das Array neu anordnen, werden die Schlüssel ausgetauscht. Da "Objekt" immer noch oben steht, wird diese Änderung für React eher als "Umbenennen" als als Neuordnung interpretiert. Die richtigen Tasten hier müßten , um sein, dann . Sie ordnen nicht immer zur Laufzeit neu, aber wenn Sie dies tun, ist dies ein Risiko. 01010
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>
    )
  }
});

Dies wird das Problem lösen.

Ramesh
quelle
0

Ich habe diese Fehlermeldung erhalten, weil <></>sie für einige Elemente im Array zurückgegeben wurde, wenn sie stattdessen zurückgegeben werden nullmuss.

Felipe
quelle
-1

Ich habe dies mit Guid für jeden Schlüssel wie folgt behoben: Guid generieren:

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);
}

Und dann diesen Wert den Markern zuweisen:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Archil Labadze
quelle
2
Das Hinzufügen einer Zufallszahl als Schlüssel ist schädlich! Dadurch wird verhindert, dass auf Änderungen reagiert wird. Dies ist der Zweck des Schlüssels.
Pihentagy
-1

Grundsätzlich sollten Sie den Index des Arrays nicht als eindeutigen Schlüssel verwenden. Stattdessen können Sie a verwenden setTimeout(() => Date.now(),0), um den eindeutigen Schlüssel oder die nicht liquide UUID oder eine andere Methode festzulegen , mit der eine eindeutige ID generiert wird, jedoch nicht der Index des Arrays als eindeutige ID.

Rohan Naik
quelle