Zrozumienie unikalnych kluczy dla potomków tablicy w React.js

655

Buduję komponent React, który akceptuje źródło danych JSON i tworzy sortowalną tabelę.
Każdy z wierszy danych dynamicznych ma przypisany unikalny klucz, ale wciąż pojawia się błąd:

Każde dziecko w tablicy powinno mieć unikalny „klucz” rekwizyt.
Sprawdź metodę renderowania TableComponent.

Moja TableComponentmetoda renderowania zwraca:

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

TableHeaderSkładnikiem jest jednorzędowe i również posiada unikalny klucz przypisany do niego.

Każdy roww rowszbudowany jest ze składnika z unikalnym kluczem:

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

I TableRowItemwygląda to tak:

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

Co powoduje unikalny błąd klucza prop?

Brett DeWoody
źródło
7
Twoje wiersze w tablicy JS powinny mieć unikalną keywłaściwość. Pomoże ReactJS znaleźć odniesienia do odpowiednich węzłów DOM i zaktualizować tylko zawartość wewnątrz narzutów, ale nie zrenderuje całej tabeli / wiersza.
Kiril
Czy możesz również współdzielić rowstablicę, a najlepiej jsfiddle? Dont potrzebują keynieruchomości na theadi tbodyna drodze.
nilgun
Dodałem komponent wiersza do pierwotnego pytania @nilgun.
Brett DeWoody
3
Czy to możliwe, że niektóre przedmioty nie mają identyfikatora lub identyfikatora?
nilgun

Odpowiedzi:

621

Powinieneś dodać klucz do każdego dziecka, a także do każdego elementu wewnątrz dzieci .

W ten sposób React może obsłużyć minimalną zmianę DOM.

W twoim kodzie każde z nich <TableRowItem key={item.id} data={item} columns={columnNames}/>próbuje wyrenderować niektóre dzieci bez klucza.

Sprawdź ten przykład .

Spróbuj usunąć element key={i}z <b></b>elementu div (i sprawdź konsolę).

W przykładzie, jeśli nie damy klucza do <b>elementu i chcemy zaktualizować tylko ten element object.city, React musi ponownie renderować cały wiersz względem samego elementu.

Oto kod:

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

Odpowiedź wysłana przez @Chris na dole jest o wiele bardziej szczegółowa niż ta odpowiedź. Proszę spojrzeć na https://stackoverflow.com/a/43892905/2325522

Reaguj dokumentację dotyczącą znaczenia kluczy w uzgodnieniu: Klucze

jmingov
źródło
5
Mam dokładnie ten sam błąd. Czy problem został rozwiązany po czacie? Jeśli tak, czy możesz wysłać aktualizację do tego pytania.
Deke
1
Odpowiedź działa, Deke. Upewnij się tylko, że keywartość rekwizytu jest unikalna dla każdego elementu i umieszczasz keyrekwizyt w komponencie, który jest najbliżej granic tablicy. Na przykład w React Native najpierw próbowałem umieścić keyrekwizyt w <Text>komponencie. Musiałem jednak umieścić go w <View>elemencie macierzystym <Text>. jeśli Array == [(Widok> Tekst), (Widok> Tekst)], musisz ustawić go w widoku. Nie tekst.
straszny
238
Dlaczego tak trudno Reactowi generować same unikalne klucze?
Davor Lucic,
13
@DavorLucic, tutaj jest dyskusja: github.com/facebook/react/issues/1342#issuecomment-39230939
koddo
5
Jest to w przybliżeniu oficjalne słowo w notatce zamieszczonej na powyższym czacie z tematem: klucze dotyczą tożsamości członka zestawu, a automatyczne generowanie kluczy dla elementów wyłaniających się z dowolnego iteratora prawdopodobnie ma wpływ na wydajność w React biblioteka.
sameers
523

Zachowaj ostrożność podczas iteracji po tablicach !!

Jest powszechnym błędnym przekonaniem, że użycie indeksu elementu w tablicy jest akceptowalnym sposobem tłumienia błędu, który prawdopodobnie znasz:

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

Jednak w wielu przypadkach tak nie jest! Jest to anty-wzór, który w niektórych sytuacjach może prowadzić do niepożądanych zachowań .


Zrozumienie keyprop

React używa keyrekwizytu, aby zrozumieć relację elementu do elementu DOM, która jest następnie wykorzystywana w procesie uzgadniania . Dlatego bardzo ważne jest, aby klucz zawsze pozostawał unikalny , w przeciwnym razie istnieje duża szansa, że ​​React pomieszać elementy i zmutować niewłaściwy. Ważne jest również, aby te klucze pozostały statyczne podczas wszystkich renderowań w celu zachowania najlepszej wydajności.

Biorąc to pod uwagę, nie zawsze trzeba stosować powyższe, pod warunkiem, że wiadomo, że tablica jest całkowicie statyczna. Jednak w miarę możliwości zaleca się stosowanie najlepszych praktyk.

Deweloper React powiedział w tym numerze GitHub :

  • kluczem nie jest tak naprawdę wydajność, lecz tożsamość (co z kolei prowadzi do lepszej wydajności). losowo przypisywane i zmieniające się wartości nie są tożsamością
  • Nie możemy realistycznie dostarczyć kluczy [automatycznie] bez wiedzy o sposobie modelowania danych. Sugerowałbym może użycie funkcji haszującej, jeśli nie masz identyfikatorów
  • Mamy już klucze wewnętrzne, gdy korzystamy z tablic, ale są one indeksem w tablicy. Po wstawieniu nowego elementu klucze są nieprawidłowe.

Krótko mówiąc, keypowinno być:

  • Unikalny - klucz nie może być identyczny z kluczem elementu rodzeństwa .
  • Statyczny - Klucz nigdy nie powinien zmieniać się między renderami.


Korzystanie z keyprop

Zgodnie z powyższym wyjaśnieniem dokładnie przestudiuj poniższe próbki i spróbuj wdrożyć, w miarę możliwości, zalecane podejście.


Źle (potencjalnie)

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

Jest to prawdopodobnie najczęstszy błąd występujący podczas iteracji nad tablicą w React. Takie podejście nie jest technicznie „złe” , jest po prostu… „niebezpieczne”, jeśli nie wiesz, co robisz. Jeśli iterujesz przez tablicę statyczną, jest to całkowicie poprawne podejście (np. Tablica linków w menu nawigacyjnym). Jeśli jednak dodajesz, usuwasz, zmieniasz kolejność lub filtrujesz elementy, musisz zachować ostrożność. Spójrz na to szczegółowe wyjaśnienie w oficjalnej dokumentacji.

W tym fragmencie używamy niestatycznej tablicy i nie ograniczamy się do używania go jako stosu. To niebezpieczne podejście (zobaczysz dlaczego). Zauważ, jak dodając elementy na początku tablicy (w zasadzie cofnij przesunięcie), wartość każdego z nich <input>pozostaje na miejscu. Dlaczego? Ponieważ keynie jednoznacznie identyfikuje każdy element.

Innymi słowy, na początku Item 1ma key={0}. Kiedy dodamy drugi element, najwyższy element staje się Item 2, a następnie Item 1jako drugi element. Jednak teraz Item 1ma key={1}i key={0}już nie ma. Zamiast tego Item 2teraz ma key={0}!!

React uważa, że <input>elementy się nie zmieniły, ponieważ Itemklawisz z 0jest zawsze na górze!

Dlaczego więc takie podejście jest czasami złe?

Takie podejście jest ryzykowne tylko wtedy, gdy tablica jest w jakiś sposób filtrowana, przestawiana lub elementy są dodawane / usuwane. Jeśli zawsze jest statyczny, to jest całkowicie bezpieczny w użyciu. Na przykład menu nawigacyjne, takie jak, ["Home", "Products", "Contact us"]może być bezpiecznie iterowane za pomocą tej metody, ponieważ prawdopodobnie nigdy nie dodasz nowych łączy ani nie zmienisz ich położenia.

W skrócie, oto kiedy możesz bezpiecznie używać indeksu jakokey :

  • Tablica jest statyczna i nigdy się nie zmieni.
  • Tablica nigdy nie jest filtrowana (wyświetla podzbiór tablicy).
  • Tablica nigdy nie jest zmieniana.
  • Tablica jest używana jako stos lub LIFO (ostatnie wejście, pierwsze wyjście). Innymi słowy, dodawanie można wykonać tylko na końcu tablicy (tj. Push) i tylko ostatni element można usunąć (np. Pop).

Gdybyśmy zamiast tego we fragmencie powyżej przesunęli dodany element na koniec tablicy, kolejność dla każdego istniejącego elementu zawsze byłaby poprawna.


Bardzo źle

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

Chociaż takie podejście prawdopodobnie zagwarantuje unikalność kluczy, zawsze wymusi reakcję w celu ponownego renderowania każdego elementu na liście, nawet jeśli nie jest to wymagane. To bardzo złe rozwiązanie, ponieważ ma duży wpływ na wydajność. Nie wspominając już o tym, że nie można wykluczyć możliwości kolizji klucza w przypadku, Math.random()gdy dwukrotnie otrzyma ten sam numer.

Niestabilne klucze (takie jak te produkowane przez Math.random()) spowodują niepotrzebne odtworzenie wielu instancji komponentów i węzłów DOM, co może spowodować pogorszenie wydajności i utratę stanu komponentów potomnych.


Bardzo dobre

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

Jest to prawdopodobnie najlepsze podejście, ponieważ wykorzystuje właściwość, która jest unikalna dla każdego elementu w zestawie danych. Na przykład, jeśli rowszawiera dane pobrane z bazy danych, można użyć klucza podstawowego tabeli ( który zwykle jest liczbą automatycznie zwiększającą wartość ).

Najlepszym sposobem na wybranie klucza jest użycie ciągu, który jednoznacznie identyfikuje element listy wśród jego rodzeństwa. Najczęściej używasz identyfikatorów z danych jako kluczy


Dobrze

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

...

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

To również dobre podejście. Jeśli zestaw danych nie zawiera żadnych danych gwarantujących unikalność ( np. Tablicę dowolnych liczb ), istnieje ryzyko kolizji klucza. W takich przypadkach najlepiej jest ręcznie wygenerować unikalny identyfikator dla każdego elementu w zestawie danych przed iteracją. Najlepiej podczas montowania komponentu lub odbierania zestawu danych ( np. Z propslub z wywołania asynchronicznego interfejsu API ), aby zrobić to tylko raz , a nie za każdym razem, gdy komponent jest ponownie renderowany. Istnieje już kilka bibliotek, które mogą dostarczyć takie klucze. Oto jeden przykład: indeks klucza reakcji .

Chris
źródło
1
W oficjalnych dokumentach używają toString()do konwersji na ciąg zamiast pozostawić jako liczbę. Czy to ważne, aby pamiętać?
skube
1
@skube, nie, możesz również używać liczb całkowitych key. Nie jestem pewien, dlaczego to konwertują.
Chris
1
Chyba można używać liczb całkowitych, ale powinien pan? W swoich dokumentach stwierdzają „... najlepszym sposobem na wybranie klucza jest użycie łańcucha, który jednoznacznie identyfikuje ...” (moje podkreślenie)
skube
2
@skube, tak, jest to całkowicie do przyjęcia. Jak stwierdzono w powyższych przykładach, możesz użyć indeksu elementu tablicy iterowanej (i to jest liczba całkowita). Nawet dokumenty mówią: „W ostateczności możesz przekazać indeks elementu do tablicy jako klucz” . Jednak dzieje się tak, że keyzawsze kończy się to ciągiem.
Chris
3
@ farmcommand2, klucze są stosowane do React Components i muszą być unikalne wśród rodzeństwa . Jest to określone powyżej. Innymi słowy, unikatowy w tablicy
Chris
8

To może komuś pomóc, ale może to być szybkie odniesienie. Jest to również podobne do wszystkich odpowiedzi przedstawionych powyżej.

Mam wiele lokalizacji, które generują listę przy użyciu poniższej struktury:

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

Po krótkiej próbie i błędzie (i niektórych frustracjach) dodanie właściwości klucza do najbardziej zewnętrznego bloku rozwiązało to. Zauważ też, że tag <> jest teraz zastąpiony tagiem teraz.

return (

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

Oczywiście naiwnie używam indeksu iteracji (indeksu), aby wypełnić kluczową wartość w powyższym przykładzie. Idealnie byłoby użyć czegoś, co jest unikalne dla elementu listy.

apil.tamang
źródło
To było bardzo pomocne, dziękuję! Nawet nie zdawałem sobie sprawy, że muszę to umieścić w najbardziej zewnętrznej warstwie
Charles Smith,
6

Ostrzeżenie: każde dziecko w tablicy lub iteratorze powinno mieć unikalny „klucz” prop.

To ostrzeżenie, ponieważ dla elementów tablicy, nad którymi będziemy się powtarzać, będzie potrzebne unikalne podobieństwo.

React obsługuje iteracyjne renderowanie komponentów jako tablice.

Lepszym sposobem na rozwiązanie tego problemu jest zapewnienie indeksu elementów tablicy, które będą iterowane. Na przykład:

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

indeks jest React wbudowane rekwizyty.

Shashank Malviya
źródło
2
Takie podejście jest potencjalnie niebezpieczne, jeśli elementy zostaną w jakiś sposób uporządkowane. Ale jeśli pozostają statyczne, to jest w porządku.
Chris
@chris Całkowicie się z tobą zgadzam, ponieważ w tym przypadku indeks może być zduplikowany. Lepiej używać wartości dynamicznych dla klucza.
Shashank Malviya
@chris Zgadzam się również z twoim komentarzem. Powinniśmy raczej stosować wartości dynamiczne niż indeksować, ponieważ mogą istnieć duplikaty. Dla uproszczenia zrobiłem to. Btw dziękuję za twój wkład (pozytywny)
Shashank Malviya
6

Po prostu dodaj unikalny klucz do swoich komponentów

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})
Bashirpour
źródło
6

Sprawdź: klucz = undef !!!

Otrzymałeś również wiadomość ostrzegającą:

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

jeśli kod jest kompletny, ale jeśli jest włączony

<ObjectRow key={someValue} />

someValue jest niezdefiniowany !!! Najpierw sprawdź to. Możesz zaoszczędzić godziny.

Gerd
źródło
1

Najlepsze rozwiązanie zdefiniowania unikalnego klucza w reakcji: w mapie zainicjowałeś nazwę posta, następnie klucz zdefiniuj kluczem {post.id} lub w moim kodzie widzisz definiuję element nazwy, a następnie definiuję klucz według klucza = {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>

Khadim Rana
źródło
0

To ostrzeżenie, ale rozwiązanie tego problemu sprawi, że reakcje będą znacznie SZYBSZE ,

Jest tak, ponieważ Reactmusi jednoznacznie identyfikować każdą pozycję na liście. Powiedzmy, że jeśli stan elementu z tej listy zmienia się w Reactach, Virtual DOMto React musi dowiedzieć się, który element został zmieniony i gdzie w DOM musi się zmienić, aby DOM przeglądarki był zsynchronizowany z DOMem Reactów wirtualnym.

Jako rozwiązanie wystarczy wprowadzić keyatrybut do każdego litagu. To keypowinno być unikalną wartość dla każdego elementu.

główny
źródło
To nie jest do końca poprawne. Renderowanie nie będzie szybsze, jeśli dodasz keyrekwizyt. Jeśli go nie podasz, React przypisze je automatycznie (aktualny indeks iteracji).
Chris
@Chris w takim przypadku, dlaczego wywołuje ostrzeżenie?
pierwsza
ponieważ nie podając klucza, React nie wie, w jaki sposób modelowane są Twoje dane. Może to prowadzić do niepożądanych wyników, jeśli tablica zostanie zmodyfikowana.
Chris
@Chris w takim przypadku modyfikacji tablicy, zareaguje poprawiając indeksy zgodnie z tym, jeśli nie dostarczyliśmy kluczy. W każdym razie myślałem, że usunięcie dodatkowego obciążenia z React będzie miało pewien wpływ na proces renderowania.
pierwsza
znowu React w zasadzie zrobi key={i}. To zależy od danych, które zawiera tablica. Na przykład, jeśli masz listę ["Volvo", "Tesla"], to oczywiście Volvo jest identyfikowane przez klucz, 0a Tesla z 1- ponieważ jest to kolejność, w jakiej pojawią się w pętli. Teraz, jeśli zmienisz kolejność tablicy, klucze zostaną zamienione. W przypadku React, ponieważ „obiekt” 0wciąż znajduje się na górze, zinterpretuje tę zmianę raczej jako „zmianę nazwy”, a nie zmianę kolejności. Odpowiednie klawisze tu musiałby być w porządku, 1wtedy 0. Nie zawsze zmieniasz kolejność w trakcie działania, ale kiedy to robisz, jest to ryzyko.
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>
    )
  }
});

To rozwiąże problem.

Ramesh
źródło
0

Natrafiłem na ten komunikat o błędzie, ponieważ w <></>zamian za niektóre elementy w tablicy nulltrzeba było zwrócić.

Felipe
źródło
-1

Naprawiłem to za pomocą Guid dla każdego takiego klucza: Generowanie 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);
}

A następnie przypisując tę ​​wartość do znaczników:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Archil Labadze
źródło
2
Dodanie losowej liczby jako klucza jest szkodliwe! Zapobiegnie reagowaniu na wykrywanie zmian, co jest celem klucza.
pihentagy
-1

Zasadniczo nie należy używać indeksu tablicy jako unikalnego klucza. Raczej można użyć setTimeout(() => Date.now(),0), aby ustawić unikalny klucz lub uniquid UUID lub w jakikolwiek inny sposób, który wygeneruje unikatowy identyfikator, ale nie indeks tablicy jako unikalnego identyfikatora.

Rohan Naik
źródło