Jika anda pernah meluangkan masa dengan React, anda pasti pernah melihat amaran kuning dalam konsol anda: “Each child in a list should have a unique ‘key’ prop.” Ia kedengaran seperti cadangan yang sopan, tetapi React sebenarnya memberi amaran bahawa ia tidak dapat membezakan item senarai anda. Abaikan ia, dan anda akhirnya akan menghantar pepijat yang sangat sukar untuk dihasilkan semula—keadaan (state) melompat ke baris yang salah, input teks hilang fokus, atau animasi tercetus pada elemen yang salah.
Enjin rendering React tidak membandingkan UI anda piksel demi piksel. Ia membina pokok objek ringan yang dipanggil virtual DOM, membandingkan pokok baharu dengan pokok sebelumnya, dan mengira set perubahan terkecil yang diperlukan untuk DOM sebenar. Apabila anda merender senarai, React melihat tatasusunan (array) elemen adik-beradik. Tanpa kunci (keys), ia tidak mempunyai cara yang boleh dipercayai untuk mengetahui sama ada sesuatu item telah berpindah, diganti, atau dibuang. Ia secara lalai memadankan mengikut kedudukan, yang mana ia sangat rapuh. Keys bertindak sebagai identiti yang stabil. Ia memberitahu React, “Elemen ini adalah elemen yang sama seperti sebelum ini, walaupun ia kini berada di slot yang berbeza.” Jika anda tersilap dalam hal ini, anda menukar kemas kini deterministik dengan tekaan semata-mata.
Pembaikan Minimum
Amaran ini biasanya muncul di dalam panggilan map. Anda mesti menetapkan nilai unik kepada atribut key pada elemen tahap teratas yang dikembalikan daripada iterator.
Berikut adalah corak yang anda lihat dalam setiap kod sumber yang mencetuskan amaran tersebut:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li>
))}
</ul>
);
};
React melihat tiga tag <li dan tidak tahu yang mana satu adalah yang mana. Pembetulannya adalah satu atribut:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
key mesti ditetapkan pada elemen secara langsung di dalam callback map. Jika anda mengekstrak <li ke dalam komponen UserItem yang berasingan, key tersebut tetap perlu berada pada komponen di tapak panggilan:
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
Meletakkan key di dalam UserItem pada <div dalamannya tidak akan menghilangkan amaran tersebut dan tidak akan membetulkan tingkah laku rekonsiliasi (reconciliation). React mencari key pada elemen yang dikembalikan oleh iterator.
Mengapa Indeks sebagai Key Adalah Berbahaya
Adalah menggoda untuk menghilangkan amaran tersebut dengan menggunakan argumen kedua map:
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
Ini menghilangkan gangguan pada konsol, tetapi ia tidak menyelesaikan masalah asas. Indeks tatasusunan (array) bukanlah identiti. Ia adalah kedudukan, dan kedudukan boleh berubah.
Bayangkan senarai tiga pengguna yang dipaparkan mengikut urutan ini:
- Alice (indeks 0)
- Bob (indeks 1)
- Charlie (indeks 2)
Jika anda memadam Alice, Bob beralih ke indeks 0 dan Charlie beralih ke indeks 1. React membandingkan pokok baharu dengan pokok lama. Ia melihat bahawa indeks 0 kini memegang data Bob, jadi ia mengubah (mutate) nod DOM sedia ada yang sebelum ini memaparkan Alice. Jika nod tersebut mempunyai fokus, kursor akan kekal di baris pertama, tetapi teks bertukar kepada Bob. Jika baris tersebut mengandungi <input> dengan state tempatan, state tersebut akan kekal terikat pada indeks 0. Pengguna menaip ke dalam apa yang kelihatan seperti baris Bob, tetapi state tersebut milik Alice. Kekacauan yang sama berlaku apabila anda menyusun (sort), menapis (filter), atau menambah item di hadapan (prepend). Satu-satunya tempat yang selamat untuk kunci indeks adalah senarai yang benar-benar statik: tiada penyusunan semula, tiada penapisan, tiada penyisipan, dan tiada pemadaman. Pautan navigasi yang dikodkan secara tetap (hardcoded) yang tidak pernah berubah adalah contoh yang sesuai. Segalanya yang lain memerlukan pengenal pasti (identifier) yang sebenar.
Di Mana Hendak Mencari Key yang Stabil
Key terbaik ialah pengenal pasti unik yang sudah sedia ada dalam model data anda. Kunci utama (primary key) pangkalan data seperti id adalah ideal kerana ia dijamin unik dan kekal merentasi render. Jika backend anda mengembalikan objek dengan uuid, slug, atau medan unik semula jadi yang lain, gunakan itu sebagai ganti.
Apabila respons API anda tidak mempunyai sebarang medan unik, anda mempunyai dua jalan praktikal. Pertama, berbincanglah dengan pasukan backend anda dan minta mereka menyertakan id. Menghantar data hubungan (relational data) tanpa kunci utama adalah satu amalan yang buruk (code smell), dan membetulkannya di punca akan menghapuskan kekaburan di seluruh stack anda. Kedua, jika anda menjana item sepenuhnya di bahagian klien—contohnya, senarai tugasan (todo list) di mana pengguna mencipta tugasan sebelum apa-apa dihantar ke pelayan—jana ID sekali sahaja semasa masa penciptaan. Pustaka (libraries) seperti uuid atau nanoid dibina khusus untuk tujuan ini. Jana ID apabila pengguna menghantar borang, simpan ia pada objek, dan gunakan ia sebagai key selama-lamanya.
Jangan sekali-kali menjana key di dalam laluan render. Memanggil Math.random() atau Date.now() semasa render komponen akan menghasilkan nilai baharu pada setiap laluan. React melihat key baharu, menganggapnya sebagai elemen yang benar-benar baharu, memusnahkan nod DOM lama, dan mencipta yang baharu. Sebarang state di dalam elemen tersebut akan ditetapkan semula (reset). Fokus akan hilang. Prestasi akan merosot kerana React melakukan kerja DOM yang tidak perlu. Key yang dijana secara rawak adalah lebih buruk daripada tiada key langsung.
Fragments, Komponen, dan Skop
Satu perangkap yang kurang jelas melibatkan React Fragments. Jika anda melakukan pemetaan (map) ke atas data dan perlu mengembalikan beberapa elemen sibling tanpa pembungkus <div>, anda mungkin akan menggunakan sintaks ringkas:
{items.map((item) => (
<>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</>
))}
Singkatan <>...</> tidak menyokong props, yang bermaksud anda tidak boleh menyertakan kunci (key). Dalam kes ini, tukar kepada sintaks eksplisit penuh:
{items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
React memerlukan kunci tersebut pada Fragment supaya ia dapat menjejaki pasangan tersebut sebagai satu unit tunggal merentasi render.
Satu lagi perkara halus: kunci (keys) bukanlah props dalam pengertian biasa. Jika anda menulis <ListItem key={item.id} />, komponen ListItem tidak boleh membaca props.key. React menggunakannya secara dalaman untuk urusan rekod (bookkeeping). Jika komponen anda benar-benar memerlukan pengenal pasti (identifier) tersebut untuk logiknya sendiri, hantarkannya secara berasingan di bawah nama lain, seperti itemId.
Peraturan Praktikal untuk Menjaga Keselamatan Anda
- Utamakan ID pangkalan data. Ia adalah unik, berasaskan nombor atau rentetan (string), dan stabil.
- Gunakan
uuidataunanoiduntuk data client-only. Jana ID sekali sahaja apabila rekod dicipta, bukan di dalam render komponen. - Jangan sesekali menderivasi kunci daripada indeks tatasusunan (array index) jika senarai boleh berubah. Penyusunan (sorting), penapisan (filtering), dan pemadaman akan memperkenalkan pepijat visual dan keadaan (state).
- Jangan sesekali menggunakan
Math.random(),Date.now(), atau sebarang nilai yang berubah antara render. Ini akan memaksa proses unmounting dan remounting yang tidak perlu. - Ingat bahawa Fragments memerlukan bentuk panjang jika ia berada di dalam
mapdan memerlukan kunci. - Letakkan kunci pada elemen yang dikembalikan oleh
map, bukan di dalam komponen anak.
Kesimpulan Utama
Prop kunci bukanlah sekadar peraturan lint hiasan. Ia adalah cara React mengekalkan identiti merentasi render. Anggap ia seperti kunci utama (primary key) dalam jadual pangkalan data. Apabila identiti tersebut stabil, React dapat menggerakkan, mengemas kini, dan membuang elemen dengan tepat. Apabila ia hilang atau tidak stabil, anda akan menanggung akibatnya dengan keadaan UI yang rosak dan penyelarasan (reconciliation) yang lembap. Selesaikan ia sekali di lapisan data, dan senarai anda akan berfungsi secara boleh ramal tidak kira betapa banyak ia berkembang atau berubah.
