Memahami kunci unik untuk anak-anak array di React.js

657

Saya sedang membangun komponen Bereaksi yang menerima sumber data JSON dan membuat tabel yang bisa diurutkan.
Setiap baris data dinamis memiliki kunci unik yang ditetapkan untuknya tetapi saya masih mendapatkan kesalahan:

Setiap anak dalam array harus memiliki prop "kunci" yang unik.
Periksa metode render dari TableComponent.

TableComponentMetode render saya kembali:

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

The TableHeaderkomponen satu baris dan juga memiliki kunci yang unik yang ditugaskan untuk itu.

Setiap rowin rowsdibangun dari komponen dengan kunci unik:

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

Dan TableRowItemtampilannya seperti ini:

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

Apa yang menyebabkan kesalahan kunci prop unik?

Brett DeWoody
sumber
7
Baris Anda di array JS harus memiliki keyproperti unik . Ini akan membantu ReactJS untuk menemukan referensi ke simpul DOM yang sesuai dan memperbarui konten hanya di dalam mark-up tetapi tidak merender ulang seluruh tabel / baris.
Kiril
Bisakah Anda juga berbagi rowsarray atau lebih disukai jsfiddle? Anda tidak perlu keyproperti theaddan tbodyomong-omong.
nilgun
Saya menambahkan komponen baris ke pertanyaan awal @nilgun.
Brett DeWoody
3
Apakah mungkin beberapa item tidak memiliki id atau memiliki id yang sama?
nilgun

Jawaban:

623

Anda harus menambahkan kunci untuk setiap anak dan setiap elemen di dalam anak-anak .

Dengan cara ini Bereaksi dapat menangani perubahan DOM minimal.

Dalam kode Anda, masing-masing <TableRowItem key={item.id} data={item} columns={columnNames}/> - berusaha membuat beberapa anak di dalamnya tanpa kunci.

Lihat contoh ini .

Coba hapus key={i}dari<b></b> elemen di dalam div (dan periksa konsol).

Dalam sampel, jika kita tidak memberikan kunci untuk <b>elemen dan kami ingin memperbarui hanya satuobject.city , Bereaksi kebutuhan untuk re-render seluruh baris vs hanya elemen.

Ini kodenya:

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

Jawaban yang diposting oleh @Chris di bagian bawah jauh lebih detail daripada jawaban ini. Silakan lihat di https://stackoverflow.com/a/43892905/2325522

Bereaksi dokumentasi tentang pentingnya kunci dalam rekonsiliasi: Kunci

jmingov
sumber
5
Saya mengalami kesalahan yang sama persis. Apakah ini diselesaikan setelah obrolan? Jika demikian, dapatkah Anda memposting pembaruan untuk pertanyaan ini.
Deke
1
Jawabannya berhasil, Deke. Pastikan keynilai untuk prop adalah unik untuk setiap item dan Anda meletakkan keyprop pada komponen yang paling dekat dengan batas array. Sebagai contoh, dalam React Native, pertama saya mencoba meletakkan keyprop ke <Text>komponen. Namun saya harus meletakkannya di <View>komponen yang merupakan induk dari <Text>. jika Array == [(Lihat> Teks), (Lihat> Teks)] Anda harus meletakkannya ke Lihat. Bukan Teks.
scaryguy
240
Mengapa React sangat sulit untuk menghasilkan kunci unik itu sendiri?
Davor Lucic
5
Ini cukup banyak kata resmi pada catatan yang dibuat dalam masalah obrolan terkait dengan di atas: kunci adalah tentang identitas anggota set dan pembuatan otomatis kunci untuk item yang muncul dari iterator sewenang-wenang mungkin memiliki implikasi kinerja dalam React Perpustakaan.
sama
524

Hati-hati saat mengulangi array !!

Ini adalah kesalahpahaman umum bahwa menggunakan indeks elemen dalam array adalah cara yang dapat diterima untuk menekan kesalahan yang mungkin Anda kenal:

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

Namun, dalam banyak kasus tidak! Ini adalah anti-pola yang dalam beberapa situasi dapat menyebabkan perilaku yang tidak diinginkan .


Memahami keyprop

Bereaksi menggunakan keyprop untuk memahami hubungan Elemen-ke-DOM elemen, yang kemudian digunakan untuk proses rekonsiliasi . Oleh karena itu sangat penting bahwa kunci selalu tetap unik , jika tidak ada kesempatan bagus Bereaksi akan mencampur unsur-unsur dan bermutasi yang salah. Penting juga bahwa tombol-tombol ini tetap statis di seluruh rendering ulang untuk mempertahankan kinerja terbaik.

Yang sedang berkata, seseorang tidak selalu perlu menerapkan di atas, asalkan diketahui bahwa array benar-benar statis. Namun, menerapkan praktik terbaik dianjurkan sedapat mungkin.

Pengembang Bereaksi mengatakan dalam masalah GitHub ini :

  • kuncinya sebenarnya bukan tentang kinerja, ini lebih tentang identitas (yang pada gilirannya mengarah pada kinerja yang lebih baik). nilai yang ditugaskan secara acak dan berubah bukan identitas
  • Kami tidak dapat secara realistis memberikan kunci [otomatis] tanpa mengetahui bagaimana data Anda dimodelkan. Saya akan menyarankan mungkin menggunakan semacam fungsi hashing jika Anda tidak memiliki id
  • Kami sudah memiliki kunci internal ketika kami menggunakan array, tetapi mereka adalah indeks dalam array. Saat Anda memasukkan elemen baru, kunci-kunci itu salah.

Singkatnya, a key seharusnya:


Menggunakan key prop

Sesuai dengan penjelasan di atas, pelajari sampel-sampel berikut dengan cermat dan cobalah untuk mengimplementasikan, bila mungkin, pendekatan yang direkomendasikan.


Buruk (Berpotensi)

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

Ini bisa dibilang kesalahan paling umum yang terlihat ketika iterasi di atas array di Bereaksi. Pendekatan ini secara teknis tidak "salah" , itu hanya ... "berbahaya" jika Anda tidak tahu apa yang Anda lakukan. Jika Anda melakukan iterasi melalui array statis maka ini adalah pendekatan yang benar-benar valid (mis. Array tautan dalam menu navigasi Anda). Namun, jika Anda menambahkan, menghapus, memesan kembali, atau memfilter item, maka Anda harus berhati-hati. Lihatlah penjelasan terperinci ini dalam dokumentasi resmi.

Dalam cuplikan ini kami menggunakan array non-statis dan kami tidak membatasi diri untuk menggunakannya sebagai tumpukan. Ini adalah pendekatan yang tidak aman (Anda akan melihat alasannya). Perhatikan bagaimana saat kita menambahkan item ke awal array (pada dasarnya unshift), nilai untuk masing-masing <input>tetap di tempatnya. Mengapa? Karena keytidak mengidentifikasi setiap item secara unik.

Dengan kata lain, pada awalnya Item 1sudah key={0}. Ketika kami menambahkan item kedua, item teratas menjadi Item 2, diikuti oleh Item 1sebagai item kedua. Namun, sekarang Item 1sudah key={1}dan tidak key={0}lagi. Sebaliknya, Item 2sekarang sudah key={0}!!

Karena itu, React berpendapat bahwa <input>unsur - unsurnya tidak berubah, karena Itemkunci dengan 0selalu ada di atas!

Jadi mengapa pendekatan ini saja terkadang buruk?

Pendekatan ini hanya berisiko jika array entah bagaimana difilter, disusun ulang, atau item ditambahkan / dihapus. Jika selalu statis, maka sangat aman untuk digunakan. Misalnya, menu navigasi suka["Home", "Products", "Contact us"] dapat dengan aman diulangi dengan metode ini karena Anda mungkin tidak akan pernah menambahkan tautan baru atau mengatur ulang.

Singkatnya, inilah saatnya Anda dapat menggunakan indeks dengan aman sebagai key:

  • Array bersifat statis dan tidak akan pernah berubah.
  • Array tidak pernah difilter (menampilkan subset dari array).
  • Array tidak pernah disusun ulang.
  • Array digunakan sebagai tumpukan atau LIFO (last in, first out). Dengan kata lain, menambahkan hanya bisa dilakukan di akhir array (yaitu push), dan hanya item terakhir yang bisa dihapus (yaitu pop).

Seandainya kami, dalam cuplikan di atas, mendorong item yang ditambahkan ke akhir array, urutan untuk setiap item yang ada akan selalu benar.


Sangat buruk

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

Meskipun pendekatan ini mungkin akan menjamin keunikan kunci, itu akan selalu memaksa bereaksi untuk membuat kembali setiap item dalam daftar, bahkan ketika ini tidak diperlukan. Ini solusi yang sangat buruk karena sangat memengaruhi kinerja. Belum lagi bahwa seseorang tidak dapat mengecualikan kemungkinan tabrakan kunci dalam acara yang Math.random()menghasilkan angka yang sama dua kali.

Kunci yang tidak stabil (seperti yang diproduksi oleh Math.random()) akan menyebabkan banyak instance komponen dan node DOM tidak perlu diciptakan kembali, yang dapat menyebabkan penurunan kinerja dan kehilangan status komponen anak.


Baik sekali

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

Ini bisa dibilang pendekatan terbaik karena menggunakan properti yang unik untuk setiap item dalam dataset. Misalnya, jika rowsberisi data yang diambil dari basis data, seseorang dapat menggunakan Kunci Utama tabel ( yang biasanya merupakan nomor yang bertambah secara otomatis ).

Cara terbaik untuk memilih kunci adalah dengan menggunakan string yang secara unik mengidentifikasi item daftar di antara saudara kandungnya. Paling sering Anda akan menggunakan ID dari data Anda sebagai kunci


Baik

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

...

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

Ini juga merupakan pendekatan yang baik. Jika dataset Anda tidak berisi data apa pun yang menjamin keunikan ( misalnya, array nomor sewenang-wenang ), ada kemungkinan tabrakan kunci. Dalam kasus seperti itu, yang terbaik adalah secara manual menghasilkan pengidentifikasi unik untuk setiap item dalam dataset sebelum mengulanginya. Lebih disukai ketika memasang komponen atau ketika dataset diterima ( misalnya dari propsatau dari panggilan API async ), untuk melakukan ini hanya sekali , dan tidak setiap kali komponen dirender ulang. Sudah ada beberapa perpustakaan di luar sana yang dapat memberikan Anda kunci tersebut. Berikut ini salah satu contohnya: react-key-index .

Chris
sumber
1
Dalam dokumen resmi , mereka menggunakan toString()untuk mengonversi ke string alih-alih meninggalkan sebagai nomor. Apakah ini penting untuk diingat?
skube
1
@ skube, tidak, Anda bisa menggunakan bilangan bulat keyjuga. Tidak yakin mengapa mereka mengubahnya.
Chris
1
Saya kira Anda dapat menggunakan bilangan bulat tetapi haruskah Anda melakukannya? Sesuai dokumen mereka, mereka menyatakan "... cara terbaik untuk memilih kunci adalah dengan menggunakan string yang secara unik mengidentifikasi ..." (penekanan milikku)
skube
2
@ skube, ya itu bisa diterima. Seperti yang dinyatakan dalam contoh di atas, Anda dapat menggunakan indeks item dari array iterated (dan itu adalah integer). Bahkan dokumen menyatakan: "Sebagai pilihan terakhir, Anda dapat meneruskan indeks item dalam array sebagai kunci" . Apa yang terjadi adalah bahwa keyselalu berakhir menjadi sebuah String pula.
Chris
3
@ farmcommand2, kunci diterapkan pada React Components, dan harus unik di antara saudara kandung . Ini dinyatakan di atas. Dengan kata lain, unik dalam array
Chris
8

Ini mungkin atau tidak membantu seseorang, tetapi itu mungkin referensi cepat. Ini juga mirip dengan semua jawaban yang disajikan di atas.

Saya memiliki banyak lokasi yang menghasilkan daftar menggunakan struktur di bawah ini:

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

Setelah sedikit trial and error (dan beberapa frustrasi), menambahkan properti kunci ke blok terluar menyelesaikannya. Perhatikan juga bahwa tag <> sekarang diganti dengan tag sekarang.

return (

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

Tentu saja, saya secara naif menggunakan indeks iterasi (indeks) untuk mengisi nilai kunci dalam contoh di atas. Idealnya, Anda akan menggunakan sesuatu yang unik untuk item daftar.

apil.tamang
sumber
Ini sangat membantu, terima kasih! Saya bahkan tidak menyadari bahwa saya harus meletakkannya di lapisan terluar
Charles Smith
6

Peringatan: Setiap anak dalam array atau iterator harus memiliki prop "kunci" yang unik.

Ini adalah peringatan karena untuk item array yang akan kita ulas membutuhkan kemiripan yang unik.

Bereaksi menangani iterasi komponen rendering sebagai array.

Cara yang lebih baik untuk menyelesaikan ini adalah memberikan indeks pada item-item array yang akan Anda ulangi misalnya.

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 Bereaksi alat peraga bawaan.

Shashank Malviya
sumber
2
Pendekatan ini berpotensi berbahaya jika barang tersebut diatur ulang entah bagaimana. Tetapi jika mereka tetap statis maka ini baik-baik saja.
Chris
@ Chris Saya sepenuhnya setuju dengan Anda karena dalam hal ini indeks dapat diduplikasi. Lebih baik menggunakan nilai dinamis untuk kunci.
Shashank Malviya
@ Chris Saya juga setuju dengan komentar Anda. Kita harus menggunakan nilai dinamis daripada mengindeks karena mungkin ada duplikat. Sederhananya saya melakukan ini. Terima kasih banyak atas kontribusi Anda (terunggah)
Shashank Malviya
6

Cukup tambahkan kunci unik ke Komponen Anda

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

Periksa: key = undef !!!

Anda juga mendapat pesan peringatan:

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

jika kode Anda selesai dengan benar, tetapi jika aktif

<ObjectRow key={someValue} />

someValue tidak terdefinisi !!! Silakan periksa ini dulu. Anda dapat menghemat waktu.

Gerd
sumber
1

Solusi terbaik untuk mendefinisikan kunci unik dalam bereaksi: di dalam peta Anda menginisialisasi nama pos kemudian kunci didefinisikan dengan kunci = {post.id} atau dalam kode saya Anda lihat saya mendefinisikan item nama kemudian saya mendefinisikan kunci dengan kunci = {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
sumber
0

Ini peringatan, Tetapi mengatasi ini akan membuat Reacts memberikan lebih cepat ,

Ini karena Reactperlu mengidentifikasi secara unik setiap item dalam daftar. Katakanlah jika keadaan elemen dari daftar itu berubah dalam Bereaksi Virtual DOMmaka Bereaksi perlu mencari tahu elemen mana yang berubah dan di mana dalam DOM itu perlu berubah sehingga browser DOM akan disinkronkan dengan Reacts Virtual DOM.

Sebagai solusi, cukup perkenalkan keyatribut untuk setiap litag. Ini keyharus menjadi nilai unik untuk setiap elemen.

utama
sumber
Ini tidak sepenuhnya benar. Rendering tidak akan lebih cepat jika Anda menambahkan keyprop. Jika Anda tidak menyediakan satu, Bereaksi akan menetapkan satu secara otomatis (indeks iterasi saat ini).
Chris
@ Chris dalam hal itu mengapa itu menimbulkan peringatan?
prime
karena dengan tidak memberikan kunci, Bereaksi tidak tahu bagaimana data Anda dimodelkan. Ini dapat menyebabkan hasil yang tidak diinginkan jika array diubah.
Chris
@ Chris dalam hal modifikasi array, akan Bereaksi benar indeks sesuai dengan itu jika kita tidak memberikan kunci. Lagi pula saya pikir menghapus overhead tambahan dari React akan berdampak pada proses rendering.
prime
lagi, Bereaksi pada dasarnya akan dilakukan key={i}. Jadi itu tergantung pada data array Anda. Sebagai contoh, jika Anda memiliki daftar ["Volvo", "Tesla"], maka jelas Volvo diidentifikasi oleh kunci 0dan Tesla dengan 1- karena itu adalah urutan mereka akan muncul dalam loop. Sekarang jika Anda menyusun ulang susunan kuncinya, tombol ditukar. Untuk Bereaksi, karena "objek" 0masih di atas, ia akan lebih menafsirkan perubahan ini sebagai "ganti nama", daripada menyusun ulang. Kunci yang benar di sini perlu, dalam urutan, 1kemudian 0. Anda tidak selalu memesan ulang pertengahan runtime, tetapi ketika Anda melakukannya, itu risiko.
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>
    )
  }
});

Ini akan menyelesaikan masalah.

Ramesh
sumber
0

Saya mengalami pesan kesalahan ini karena <></>dikembalikan untuk beberapa item dalam array ketika sebaliknya nullperlu dikembalikan.

Felipe
sumber
-1

Saya memperbaiki ini menggunakan Guid untuk setiap kunci seperti ini: Menghasilkan 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);
}

Dan kemudian menetapkan nilai ini ke marker:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Archil Labadze
sumber
2
Menambahkan nomor acak sebagai kunci berbahaya! Ini akan mencegah bereaksi untuk mendeteksi perubahan, yang merupakan tujuan kunci.
pihentagy
-1

Pada dasarnya Anda tidak harus menggunakan indeks array sebagai kunci unik. Alih-alih, Anda dapat menggunakan a setTimeout(() => Date.now(),0)untuk mengatur kunci unik atau uniquid uuid atau cara lain yang akan menghasilkan id unik tetapi tidak indeks array sebagai id unik.

Rohan Naik
sumber