React.js'deki dizi çocukları için benzersiz anahtarları anlama


655

JSON veri kaynağını kabul eden ve sıralanabilir bir tablo oluşturan bir React bileşeni oluşturuyorum.
Dinamik veri satırlarının her birinin kendisine atanmış benzersiz bir anahtarı vardır, ancak hala bir hata alıyorum:

Bir dizideki her alt öğenin benzersiz bir "anahtar" desteği olmalıdır.
TableComponent'in oluşturma yöntemini kontrol edin.

Listem TableComponentyöntem döner işlemek:

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

TableHeaderBileşeni tek bir satır ve aynı zamanda kendisine atanmış benzersiz bir anahtar vardır.

Her rowyer rowsbenzersiz anahtar ile bir bileşenden oluşturulmuştur:

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

Ve TableRowItemşöyle görünüyor:

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

Benzersiz anahtar pervane hatasına neden olur?


7
JS dizisindeki satırlarınızın benzersiz bir keyözelliği olmalıdır . ReactJS'nin uygun DOM düğümlerine referanslar bulmasına ve yalnızca işaretleme içindeki içeriği güncellemesine yardımcı olur, ancak tüm tabloyu / satırı yeniden oluşturmaz.
Kiril

Ayrıca rowsdizi veya daha tercihen bir jsfiddle paylaşabilirsiniz? Bir ihtiyaç daha dont keymülk theadve tbodybu arada.
nilgun

Satır bileşenini orijinal soruya @nilgun ekledim.
Brett DeWoody

3
Bazı öğelerin bir kimliği olmaması veya aynı kimliği olması mümkün mü?
nilgun

Yanıtlar:


621

Her çocuğa ve çocukların içindeki her bir öğeye bir anahtar eklemelisiniz .

Bu şekilde React minimum DOM değişikliğini yapabilir.

Kodunuzda, her biri <TableRowItem key={item.id} data={item} columns={columnNames}/>kendi içindeki bazı çocukları anahtarsız oluşturmaya çalışıyor.

Bu örneği kontrol edin .

Kaldırmayı deneyin key={i}gelen <b></b>div içeride elemanın (ve konsol kontrol edin).

Örneğimizde, bir anahtar vermezsen <b>eleman ve güncellemek istediğiniz sadeceobject.city e ihtiyaçlarını tepki sadece eleman vs tüm satır yeniden işlemek.

İşte 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);

Tarafından gönderildi cevap @Chris altındaki bu cevap çok daha fazla ayrıntılı anlatır. Lütfen https://stackoverflow.com/a/43892905/2325522 adresine bakın.

Mutabakatta anahtarların önemi hakkında tepki belgeleri: Anahtarlar


5
Aynı hatayla karşılaşıyorum. Bu sohbetten sonra çözüldü mü? Öyleyse, lütfen bu soruya bir güncelleme gönderebilir misiniz?
Deke

1
Cevap işe yarıyor Deke. Sadece keypervane değerinin her öğe için benzersiz olduğundan emin olun ve pervaneyi keydizi sınırlarına en yakın bileşene koyuyorsunuz . Örneğin, React Native'da, önce bileşene keypervane koymaya çalışıyordum <Text>. Ancak ben onun <View>ebeveyn olan bileşen koymak zorunda kaldı <Text>. Array == [(Görünüm> Metin), (Görünüm> Metin)] onu Görünüm'e koymanız gerekir. Metin değil.
scaryguy

239
React'ın benzersiz anahtarlar üretmesi neden bu kadar zor?
Davor Lucic


5
Bu hemen hemen yukarıda bağlantılı konu sohbetinde yapılan bir nottaki resmi kelimedir : anahtarlar, bir kümenin bir üyesinin kimliği ile ilgilidir ve keyfi bir yineleyiciden çıkan öğeler için anahtarların otomatik olarak üretilmesi muhtemelen Tepki'de performans etkilerine sahiptir. kütüphane.
sameers

523

Diziler üzerinde yineleme yaparken dikkatli olun !!

Dizideki öğenin dizinini kullanmanın, muhtemelen aşina olduğunuz hatayı bastırmanın kabul edilebilir bir yolu olduğu yaygın bir yanlış anlamadır:

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

Ancak, birçok durumda değil! Bu anti-desen içinde kutu olduğunu bazı durumlara yol istenmeyen davranış .


Anlamak keypervane

React key, daha sonra mutabakat sürecinde kullanılan bileşen-DOM Öğesi ilişkisini anlamak için pervaneyi kullanır . Bu nedenle, anahtarın her zaman benzersiz kalması çok önemlidir , aksi takdirde React'ın öğeleri karıştırması ve yanlış olanı değiştirmesi için iyi bir şans vardır. En iyi performansı korumak için bu tuşların tüm yeniden oluşturma işlemlerinde statik kalması da önemlidir .

Bununla birlikte , dizinin tamamen statik olduğu biliniyorsa , yukarıdakilerin her zaman uygulanması gerekmez . Ancak, mümkün olan en iyi uygulamaların uygulanması teşvik edilir.

Bir Tepki geliştiricisi bu GitHub sayısında şunları söyledi :

  • anahtar gerçekten performansla ilgili değil, daha çok kimlikle ilgilidir (bu da daha iyi performans sağlar). rastgele atanan ve değişen değerler kimlik değildir
  • Verilerinizin nasıl modelleneceğini bilmeden gerçekçi bir şekilde [otomatik] anahtarlar sağlayamayız. Kimlikleriniz yoksa belki de bir çeşit karma işlevini kullanmanızı öneririm
  • Dizileri kullandığımızda zaten dahili anahtarlarımız var, ancak bunlar dizideki dizin. Yeni bir öğe eklediğinizde, bu tuşlar yanlış olur.

Kısacası, a şöyle keyolmalıdır:

  • Benzersiz - Anahtar, kardeş bileşeninkiyle aynı olamaz .
  • Statik - Bir anahtar, oluşturma işlemleri arasında asla değişmemelidir.


keyPervaneyi kullanma

Yukarıdaki açıklamaya göre, aşağıdaki örnekleri dikkatlice inceleyin ve mümkünse önerilen yaklaşımı uygulamaya çalışın.


Kötü (Potansiyel Olarak)

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

Bu, React'ta bir dizi üzerinden yineleme yaparken görülen en yaygın hatadır. Bu yaklaşım teknik olarak "yanlış" değil , ne yaptığınızı bilmiyorsanız sadece ... "tehlikeli" dir . Statik bir dizi üzerinden yineleme yapıyorsanız, bu tamamen geçerli bir yaklaşımdır (örneğin, navigasyon menünüzdeki bir bağlantı dizisi). Ancak, öğeleri ekler, kaldırır, yeniden sıralar veya filtreliyorsanız dikkatli olmanız gerekir. Resmi belgelerdeki bu ayrıntılı açıklamaya bir göz atın .

Bu snippet'te statik olmayan bir dizi kullanıyoruz ve kendimizi bunu yığın olarak kullanmakla kısıtlamıyoruz. Bu güvensiz bir yaklaşımdır (nedenini göreceksiniz). Dizinin başına nasıl öğe eklediğimizi (temelde unshift), her birinin değerinin <input>yerinde kaldığını unutmayın. Neden? Çünkü keyher öğeyi benzersiz şekilde tanımlamaz.

Başka bir deyişle, ilk Item 1sahiptir key={0}. İkinci öğe eklediğinizde, üst öğe olur Item 2, bunu takiben Item 1ikinci öğe olarak. Ancak, şimdi Item 1var key={1}ve key={0}artık değil. Bunun yerine, Item 2şimdi var key={0}!!

Bu nedenle, React <input>öğelerin değişmediğini düşünüyor , çünkü Itemwith tuşu 0her zaman en üstte!

Peki bu yaklaşım neden sadece bazen kötü?

Bu yaklaşım yalnızca dizi bir şekilde filtrelenir, yeniden düzenlenirse veya öğeler eklenirse / kaldırılırsa risklidir. Her zaman statikse, kullanımı tamamen güvenlidir. Örneğin, böyle bir gezinme menüsü ["Home", "Products", "Contact us"]bu yöntemle güvenli bir şekilde tekrarlanabilir, çünkü muhtemelen hiçbir zaman yeni bağlantılar eklemeyecek veya yeniden düzenlemeyeceksiniz.

Kısacası, dizini güvenli bir şekilde şu şekilde kullanabilirsiniz key:

  • Dizi statiktir ve asla değişmez.
  • Dizi asla filtrelenmez (dizinin bir alt kümesini görüntüler).
  • Dizi asla yeniden sıralanmaz.
  • Dizi bir yığın veya LIFO (son giren ilk çıkar) olarak kullanılır. Başka bir deyişle, ekleme sadece dizinin sonunda yapılabilir (yani itme) ve sadece son öğe kaldırılabilir (yani pop).

Bunun yerine, yukarıdaki snippet'te eklenen öğeyi dizinin sonuna itmiş olsaydık, mevcut her bir öğenin sırası her zaman doğru olurdu.


Çok kötü

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

Bu yaklaşım muhtemelen anahtarların benzersizliğini garanti edecek olsa da, gerekli olmasa bile listedeki her öğeyi yeniden oluşturmaya her zaman tepki vermeye zorlayacaktır. Performansı büyük ölçüde etkilediği için bu çok kötü bir çözüm. Math.random()Aynı sayıyı iki kez üreten olayda anahtar çarpışma olasılığını dışlayamayacağından bahsetmiyoruz bile .

Kararsız anahtarlar (tarafından üretilenler gibi Math.random()) birçok bileşen örneğinin ve DOM düğümünün gereksiz yere yeniden oluşturulmasına neden olarak performans düşüşüne ve alt bileşenlerde kaybolmaya neden olabilir.


Çok iyi

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

Bu, tartışmasız en iyi yaklaşımdır çünkü veri kümesindeki her öğe için benzersiz bir özellik kullanır. Örneğin, rowsbir veritabanından getirilen veriler içeriyorsa, tablonun Birincil Anahtarı ( genellikle otomatik olarak artan bir sayıdır ) kullanılabilir.

Bir anahtar seçmenin en iyi yolu, kardeşleri arasında bir liste öğesini benzersiz şekilde tanımlayan bir dize kullanmaktır. Çoğu zaman verilerinizdeki kimlikleri anahtar olarak kullanırsınız


İyi

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

...

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

Bu da iyi bir yaklaşım. Veri kümeniz benzersizliği garanti eden herhangi bir veri içermiyorsa ( örneğin, rasgele sayılar dizisi ), önemli bir çarpışma olasılığı vardır. Bu gibi durumlarda, veri kümesindeki her öğe için tekrarlamadan önce manuel olarak benzersiz bir tanımlayıcı oluşturmak en iyisidir. Tercihen komponenti monte ederken veya veri seti alındığında ( örneğin propsbir async API çağrısından veya async API çağrısından ), bunu bileşen her yeniden oluşturuşunda değil, yalnızca bir kez yapmak için. Zaten size bu tür anahtarları sağlayabilecek bir avuç kütüphane var. İşte bir örnek: tepki anahtarı dizini .


1
Gelen resmi dokümanlar , kullandıkları toString()dize yerine bir sayı olarak ayrılmadan dönüştürmek için. Bunu hatırlamak önemli mi?
skube

1
@ skube, hayır, tamsayıları da kullanabilirsiniz key. Neden dönüştürdüğünden emin değilim.
Chris

1
Ben tahmin edebilirsiniz sayıları kullanmayı ancak gerektiği sen? Dokümanlarına göre, "... bir anahtar
seçmenin

2
@ skube, evet bu kesinlikle kabul edilebilir. Yukarıdaki örneklerde belirtildiği gibi, öğenin yinelenen dizinin dizinini kullanabilirsiniz (ve bu bir tam sayıdır). Dokümanlar bile: "Son çare olarak, öğenin dizinini dizide anahtar olarak geçirebilirsiniz" . Olsa da, keyher zaman zaten bir dize olur.
Chris

3
@ farmcommand2, anahtarlar React Components'a uygulanır ve kardeşler arasında benzersiz olması gerekir . Bu yukarıda belirtilmiştir. Başka bir deyişle, dizide benzersiz
Chris

8

Bu birisine yardım edebilir veya etmeyebilir, ancak hızlı bir referans olabilir. Bu, yukarıda sunulan tüm cevaplara da benzer.

Aşağıdaki yapıyı kullanarak liste oluşturmak konumların bir sürü var:

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

Küçük bir deneme yanılma işleminden sonra (ve bazı hayal kırıklıklarından), en dıştaki bloğa anahtar özellik eklemek sorunu çözdü. Ayrıca <> etiketinin şimdi etiketle değiştirildiğini unutmayın.

return (

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

Tabii ki, yukarıdaki örnekteki anahtar değerini doldurmak için saf bir şekilde yineleme indeksini (indeks) kullanıyorum. İdeal olarak, liste öğesine özgü bir şey kullanırsınız.


Bu son derece yardımcı oldu, teşekkür ederim! En dış katmana koymak zorunda olduğumun farkında bile değildim
Charles Smith

6

Uyarı: Bir dizideki veya yineleyicideki her alt öğenin benzersiz bir "anahtar" desteği olmalıdır.

Bu, tekrarlayacağımız dizi öğeleri için benzersiz bir benzerliğe ihtiyaç duyacağı için bir uyarıdır.

React, yinelenen bileşen oluşturmayı diziler olarak işler.

Bunu çözmenin daha iyi bir yolu, tekrarlayacağınız dizi öğelerinde dizin sağlamaktır. Örneğin:

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

endeksi Tepki yerleşik sahne.


2
Bu yaklaşım, öğeler bir şekilde yeniden düzenlenirse potansiyel olarak tehlikelidir. Ama eğer statik kalırlarsa, bu iyi.
Chris

@chris Size tamamen katılıyorum çünkü bu durumda dizin çoğaltılabilir. Anahtar için dinamik değerler kullanmak daha iyidir.
Shashank Malviya

@chris Ben de senin yorumuna katılıyorum. Yinelenmeler olabileceğinden, dizin yerine dinamik değerler kullanmalıyız. Basitleştirmek için bunu yaptım. Btw katkınız için teşekkürler (upvoted)
Malviya

6

Sadece eklemek benzersiz bir anahtar sizin Bileşenleri için

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

6

Kontrol edin: anahtar = undef !!!

Ayrıca uyarı mesajını aldınız:

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

kodunuz tamsa doğru, ancak açıksa

<ObjectRow key={someValue} />

someValue undefined !!! Lütfen önce bunu kontrol edin. Saat kazanabilirsiniz.


1

Reaksiyonda benzersiz anahtar tanımlamanın en iyi çözümü: haritanın içinde ad postasını başlattınız, sonra anahtar = {post.id} ile tanımlayın veya kodumda, ad öğesini tanımladığım sonra anahtarı anahtarla tanımladım = {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

Bu bir uyarıdır, ancak bunu ele almak Tepkileri çok daha hızlı hale getirir ,

Bunun nedeni React, listedeki her öğeyi benzersiz bir şekilde tanımlaması gerektiğidir. Diyelim ki, bu listenin bir öğesinin durumu Virtual DOMReaksiyonlar'da değişirse, React'ın hangi öğenin değiştiğini ve DOM'da nerede değişmesi gerektiğini anlaması gerekir, böylece DOM tarayıcısı Reacts Sanal DOM ile senkronize olur.

Çözüm olarak key, her bir lietikete bir özellik sunun. Bu key, her bir öğe için benzersiz bir değer olmalıdır.


Bu tamamen doğru değil. Pervaneyi eklerseniz oluşturma daha hızlı olmazkey . Birini belirtmezseniz, React otomatik olarak bir tane atayacaktır (yinelemenin geçerli dizini).
Chris

@Chris bu durumda neden uyarı veriyor?
asal

çünkü bir anahtar sağlayarak React verilerinizin nasıl modelleneceğini bilmiyor. Dizi değiştirilirse bu istenmeyen sonuçlara yol açabilir.
Chris

@Chris, dizi değişikliği durumunda, anahtarlar sağlamadıysak, indeksleri buna göre düzeltir. Her neyse, React'ten fazladan ek yükü kaldırmanın oluşturma süreci üzerinde bir miktar etkisi olacağını düşündüm.
asal

yine, React temel olarak yapacak key={i}. Bu yüzden dizinizin içerdiği verilere bağlıdır. Eğer listeniz varsa Örneğin, ["Volvo", "Tesla"]o da belli ki Volvo edilir tespit tuşla 0ile Tesla 1- onlar döngü içinde görünecek düzenden dolayı. Şimdi diziyi yeniden sıralarsanız, anahtarlar değiştirilir. React için, "nesne" 0hala en üstte olduğundan, bu değişikliği yeniden sıralamak yerine bir "yeniden adlandırma" olarak yorumlayacaktır. Doğru Anahtarlar burada, sırayla, olması gerekir 1o zaman 0. Her zaman çalışma zamanının ortasında yeniden sipariş vermezsiniz, ancak bunu yaptığınızda, bu bir risktir.
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>
    )
  }
});

Bu sorunu çözecektir.


0

Bunun <></>yerine nulldöndürülmesi gerektiğinde dizideki bazı öğeler için döndürülmesinden dolayı bu hata iletisine çalışıyordum .


-1

Bu gibi her anahtar için Guid kullanarak bu düzeltildi:

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

Ve sonra bu değeri işaretleyicilere atayın:

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

2
Anahtar olarak rastgele bir sayı eklemek zararlıdır! Anahtarın amacı olan değişiklikleri tespit etmek için reaksiyonu önleyecektir.
19'da pihentagy

-1

Temel olarak dizinin dizinini benzersiz bir anahtar olarak kullanmamalısınız. Bunun yerine a'yısetTimeout(() => Date.now(),0) benzersiz anahtar veya benzersiz uuid veya benzersiz kimlik üretecek ancak dizinin dizinini benzersiz kimlik olarak oluşturmayacak başka bir yol ayarlamak için kullanabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.