Topik temu duga representatif

Temu Duga Frontend: Bagaimanakah Kunci React Mengawal Pengekalan Keadaan dan Pemasangan Semula?

FrontendSederhana
Pasukan Editorial Offer.ccDiterbitkan Dikemas kini

Soalan

Satu halaman React mempunyai senarai boleh diedit yang membolehkan pengguna mengisih, menapis, dan menyisip data ke dalamnya. Setiap baris menyimpan draf tempatan dan keadaan kembangan (expanded state). Halaman ini juga bertukar antara borang butiran untuk pengguna yang berbeza. Terangkan cara kunci mempengaruhi identiti komponen dan pengekalan keadaan, mengapa indeks tatasusunan atau nilai rawak menyebabkan pepijat, dan bilakah menukar kunci merupakan cara yang betul untuk memasang semula (remount) subpokok.

Soalan dan Senario yang Berkenaan

Satu halaman pentadbir memaparkan senarai pengguna yang boleh diedit. Setiap Row memiliki draf input yang belum dihantar dan keadaan kembangan. Pengguna boleh menyisip rekod di bahagian atas, memadam rekod di bahagian tengah, mengisih, dan menapis. Borang butiran di sebelah senarai tersebut perlu mengosongkan keadaan tempatan pengguna sebelumnya setiap kali userId bertukar.

Terangkan:

  1. Cara React menentukan sama ada sesuatu komponen adalah komponen yang sama antara pemaparan (render) bersebelahan.
  2. Mengapa key={index} boleh memindahkan draf atau keadaan kembangan kepada rekod lain.
  3. Mengapa key={Math.random()} berulang kali memasang semula komponen.
  4. Bilakah ID domain yang stabil perlu mengekalkan keadaan dan bilakah menukar kunci perlu menetapkan semula subpokok.
  5. Cara mengesahkan tingkah laku merentasi penyisipan, pemadaman, pengisihan, penapisan, dan pertukaran entiti.

Bahan temu duga React yang diterbitkan pada Mac dan Mei 2026 secara langsung merangkumi kunci, kunci indeks, penyelarasan (reconciliation), dan pemasangan semula (remounting). Walaupun gesaan ini kelihatan seperti sintaks senarai, ia menguji sama ada calon boleh memodelkan entiti logik yang memiliki keadaan dan menyatakan model tersebut melalui sempadan komponen dan ujian.

Ini adalah soalan frontend kerana kecekapan terasnya ialah identiti komponen React, jangka hayat keadaan, dan penggunaan semula DOM.

Perkara yang Dinilai oleh Penemu Duga

Pertama, calon perlu mengetahui bahawa keadaan tidak dipautkan secara automatik kepada tag JSX atau objek domain. React mengaitkan keadaan dengan kedudukan dalam pokok pemaparan. Di dalam satu induk, jenis elemen dan kunci membantu menentukan sama ada anak lama dan baharu mewakili identiti yang sama.

Kedua, calon mesti membezakan antara pemaparan semula (re-render) dan pemasangan semula (remount). Prop baharu atau kemas kini induk boleh memaparkan semula identiti yang sama sambil mengekalkan keadaan tempatannya. Jenis komponen atau kunci yang diubah akan mencipta identiti baharu, memusnahkan keadaan tempatan subpokok lama, dan mungkin mencipta semula DOMnya.

Ketiga, calon perlu memperoleh kunci daripada identiti domain. ID pangkalan data atau UUID yang dijana dan disimpan semasa rekod dicipta biasanya bermaksud "ini masih rekod yang sama." Kedudukan tatasusunan, cap masa semasa, atau nilai rawak yang dihasilkan semasa pemaparan tidak membawa maksud sedemikian.

Keempat, jawapan yang mantap tidak menjadikan "jangan sesekali menukar kunci" sebagai satu peraturan tetap. Apabila borang butiran bertukar daripada pengguna A kepada pengguna B, borang-borang tersebut mungkin mewakili entiti domain yang berbeza. key={userId} pada sempadan yang betul boleh menetapkan semula keseluruhan subpokok borang dengan lebih andal berbanding mengosongkan pemboleh ubah keadaan individu dalam Effect.

Akhir sekali, calon perlu menyatakan kos pemasangan semula: fokus, kedudukan tatal, input yang belum dihantar, dan keadaan keturunan semuanya boleh hilang. Jika produk mesti memulihkan draf apabila sesuatu entiti dipilih semula, angkat draf tersebut ke atas (lift the draft), simpannya mengikut entiti, atau kekalkannya secara luaran daripada bergantung pada keadaan tempatan dalam subpokok yang dialih keluar.

Soalan Penjelasan Sebelum Menjawab

  • Bolehkah senarai menyisip, memadam, mengisih, atau menapis item? Sebaik sahaja keahlian atau susunan boleh berubah, indeks tatasusunan tidak lagi mengenal pasti entiti domain secara stabil.
  • Adakah setiap baris memiliki keadaan React atau DOM pelayar? Input, keadaan kembangan, animasi, dan medan tidak terkawal mendedahkan isu penggunaan semula yang salah dengan cepat.
  • Adakah data mempunyai ID stabil yang unik dalam kalangan adik-beradiknya? Kunci memerlukan keunikan sesama adik-beradik, bukan keunikan global.
  • Patutkah pertukaran entiti membuang atau memulihkan drafnya? Membuang draf sesuai dengan penukaran kunci; pemulihan memerlukan lapisan keadaan yang bertahan lebih lama.
  • Patutkah keseluruhan subpokok ditetapkan semula atau hanya satu medan? Gunakan kunci untuk penetapan semula identiti sepenuhnya; pilih keadaan terkawal atau kemas kini data yang jelas untuk pelarasan separa.
  • Bilakah ID dijana? Rekod tempatan boleh menerima UUID apabila ia dicipta dan menyimpannya. Jangan jana yang baharu pada setiap pemaparan.

Rangka Kerja Jawapan 30 Saat

"React mengaitkan keadaan dengan kedudukan dalam pokok pemaparan. Di bawah induk yang sama, jenis komponen dan kunci membantu React menentukan sama ada nod lama dan baharu mempunyai identiti yang sama. Kunci yang stabil membolehkan keadaan baris mengikut rekod domain walaupun ia berpindah tempat. Dengan indeks tatasusunan, penyisipan atau pengisihan boleh meletakkan rekod yang berbeza dalam slot yang sama, menyebabkan draf tempatan berpindah ke baris yang salah. Kunci rawak pula tidak pernah sepadan dengan pemaparan sebelumnya, jadi React berulang kali mencipta semula komponen dan DOM.

Senarai harus menggunakan ID yang stabil daripada data. Jika pertukaran daripada borang butiran pengguna A kepada pengguna B mesti mengosongkan semua keadaan tempatan, saya akan meletakkan key={userId} pada sempadan subpokok borang untuk menyatakan bahawa ini ialah entiti baharu. Jika draf setiap pengguna mesti dikekalkan, saya akan mengangkat draf ke atas dan menyimpannya mengikut userId sambil mengekalkan sempadan identiti. Saya akan mengesahkan penyisipan, pemadaman, penyusunan semula, penapisan, pemaparan semula dengan ID yang sama, dan pertukaran dengan ID berbeza untuk membuktikan bahawa keadaan mengikut entiti yang dimaksudkan."

Penjelasan Mendalam Langkah demi Langkah

Langkah 1: Bina model identiti komponen.

Model temu duga yang berguna ialah:

Hubungan antara pemaparan lama dan baharuHasil biasa
Induk yang sama, jenis komponen yang sama, kunci yang samaKekalkan keadaan tempatan identiti tersebut; komponen boleh dipaparkan semula
Induk yang sama, jenis yang sama, kunci berbezaAlih keluar identiti lama, pasang yang baharu, dan tetapkan semula keadaan subpokok
Kedudukan yang sama, jenis komponen berbezaGantikan subpokok lama dan tetapkan semula keadaan
Senarai tanpa kunci eksplisitBerbalik kepada pemadanan kedudukan, yang tidak selamat untuk senarai dinamik

key bukanlah prop biasa yang dihantar kepada komponen. Ia merupakan petunjuk (hint) untuk React. Jika Row juga memerlukan ID domain, hantarkan prop yang berasingan seperti rowId={item.id}.

Kunci juga mentakrifkan identiti hanya dalam induk semasanya. Dua senarai berasingan kedua-duanya boleh mengandungi key="user-42", manakala adik-beradik di dalam satu senarai tidak boleh mempunyai kunci pendua.

Langkah 2: Hasilkan semula pepijat kunci indeks dengan senarai boleh diedit.

Pelaksanaan yang tidak betul:

tsx
function UserList({ users }: { users: User[] }) {
  return users.map((user, index) => (
    <EditableRow key={index} user={user} />
  ))
}

Andaikan susunan awal ialah [Alice, Bob]. EditableRow pada indeks 0 menyimpan draf Alice. Sisipkan Zoe di bahagian atas, menghasilkan [Zoe, Alice, Bob]. React boleh memadankan baris lama dengan kunci 0 kepada item baharu pada indeks 0, jadi keadaan milik Alice mungkin muncul dalam baris Zoe. Keadaan pada kunci 1 juga boleh berpindah daripada Bob kepada Alice dengan cara yang sama.

Masalahnya bukan kerana indeks ialah nombor. Masalahnya ialah indeks mengenal pasti slot sedangkan produk perlu mengenal pasti pengguna. Pemadaman, pengisihan, dan penapisan semuanya mengubah pemetaan antara slot dan entiti.

Pelaksanaan yang betul:

tsx
function UserList({ users }: { users: User[] }) {
  return users.map((user) => (
    <EditableRow key={user.id} rowId={user.id} user={user} />
  ))
}

Selepas Zoe disisipkan, Alice masih mempunyai ID Alice. Dia boleh berpindah daripada indeks 0 ke indeks 1, dan React boleh terus memadankan keadaan komponen Alice kepada Alice.

Jika satu rekod mengembalikan beberapa nod adik-beradik, sintaks pendek Fragment tidak boleh menerima kunci. Gunakan Fragment yang eksplisit:

tsx
import { Fragment } from 'react'

users.map((user) => (
  <Fragment key={user.id}>
    <UserHeading user={user} />
    <EditableRow user={user} />
  </Fragment>
))

Langkah 3: Pilih kunci yang stabil dan bukannya menghasilkannya semasa pemaparan.

Urutan keutamaan praktikal ialah:

  1. ID rekod yang stabil daripada backend atau pangkalan data.
  2. Pengenal pasti unik yang telah dilampirkan pada data dan tidak berubah sepanjang jangka hayat domainnya.
  3. Untuk rekod baharu tempatan sahaja, UUID yang dijana dalam peristiwa penciptaan rekod dan disimpan bersama rekod tersebut.
  4. Kunci komposit hanya apabila medan-medannya benar-benar membentuk identiti domain yang tidak boleh diubah (immutable) dan unik sesama adik-beradik.

Kod berikut mencipta identiti baharu pada setiap pemaparan:

tsx
<EditableRow key={Math.random()} user={user} />

Ini bukan pemaparan semula biasa. React mengalih keluar baris lama dan memasang baris baharu. Keadaan tempatan dan input pengguna hilang, dan DOM dicipta semula. Date.now() atau memanggil crypto.randomUUID() semasa pemaparan mempunyai masalah yang sama. UUID adalah baik apabila dijana sekali semasa penciptaan item dan disimpan, bukan apabila dijana semasa memaparkan item tersebut.

Indeks tatasusunan hanya boleh diterima di bawah kontrak yang sempit: keahlian dan susunan kekal tetap sepanjang jangka hayat senarai, tiada penyisipan, pemadaman, penapisan, atau penyusunan semula, kedudukan itu sendiri ialah identiti, dan tiada keadaan yang perlu mengikut entiti domain. Data perniagaan dinamik harus menerima ID sebenar dan bukannya bergantung pada andaian tersebut.

Langkah 4: Gunakan kunci untuk menyatakan "ini ialah borang yang berbeza."

Percubaan pembetulan yang biasa dilakukan pada halaman butiran ialah:

tsx
function Profile({ userId }: { userId: string }) {
  const [comment, setComment] = useState('')

  useEffect(() => {
    setComment('')
  }, [userId])

  return <CommentForm value={comment} onChange={setComment} />
}

Ini pada mulanya memaparkan pokok dengan komen lapuk dan kemudian mencetuskan pemaparan lain selepas Effect dijalankan. Lebih penting lagi, halaman butiran yang kompleks juga boleh mengandungi lampiran, ralat pengesahan, dan keadaan borang bersarang. Mengosongkan satu pemboleh ubah comment tidak menjamin bahawa keseluruhan subpokok akan ditetapkan semula.

Jika produk mentakrifkan borang setiap pengguna sebagai entiti yang berbeza, letakkan kunci pada sempadan identiti:

tsx
function ProfilePage({ userId }: { userId: string }) {
  return <ProfileForm key={userId} userId={userId} />
}

Apabila userId bertukar, React menganggap ProfileForm baharu sebagai identiti yang berbeza dan menetapkan semula keadaan tempatannya serta semua keadaan keturunannya. Apabila keadaan induk yang tidak berkaitan menyebabkan pemaparan semula dengan userId yang sama, kunci kekal stabil dan keadaan tempatan dipelihara.

Letakkan kunci pada sempadan lengkap terkecil yang mesti ditetapkan semula. Meletakkan kunci pada keseluruhan halaman juga akan mencipta semula navigasi, paparan yang memerlukan pengiraan tinggi, dan keadaan tatal yang tidak berkaitan. Meletakkan kunci pada satu input mungkin meninggalkan keadaan lain daripada borang yang sama.

Langkah 5: Tentukan perkara yang mesti dipelihara oleh produk sebelum memilih pemasangan semula.

"Menukar entiti" tidak semestinya bermaksud "membuang draf." Gunakan jadual keputusan:

Semantik produkReka bentuk keadaan yang disyorkan
Entiti lain mesti menerima borang yang baharu sepenuhnyaGunakan ID entiti sebagai kunci subpokok borang
Kembali ke sesuatu entiti mesti memulihkan drafnyaKekalkan kunci identiti; angkat draf ke atas dan simpannya mengikut ID
Muat semula halaman juga mesti memulihkan drafKekalkannya secara luaran dengan peraturan tamat tempoh dan pembersihan
Tetapkan semula satu medan sambil mengekalkan yang lainGunakan keadaan terkawal atau kemas kini yang jelas; jangan pasang semula subpokok
Prop hanya mengubah nilai paparan terbitanKira daripada prop semasa pemaparan dan bukannya menyalin ke dalam keadaan

Kunci mentakrifkan sempadan identiti; ia tidak menyediakan pengekalan jangka panjang. Sebaik sahaja keadaan diangkat ke dalam drafts[userId], subpokok borang boleh dinyahpasang (unmount) sementara drafnya kekal dalam induk. Memilih pengguna itu semula boleh memulakan atau mengawal borang dengan nilai yang disimpan.

Langkah 6: Kenal pasti punca lain bagi penetapan semula yang tidak disengajakan.

Walaupun dengan kunci yang betul, menukar jenis komponen akan menetapkan semula keadaan. Menukar kedudukan pokok yang sama daripada ProfileForm kepada LoginPrompt menggantikan subpokok tersebut.

Satu lagi kesilapan biasa ialah mentakrifkan komponen di dalam komponen lain:

tsx
function ProfilePage() {
  function ProfileForm() {
    const [name, setName] = useState('')
    return <input value={name} onChange={(event) => setName(event.target.value)} />
  }

  return <ProfileForm />
}

Setiap pemaparan ProfilePage mencipta objek fungsi ProfileForm yang baharu. React melihat jenis komponen yang berbeza dan menetapkan semula keadaan input secara tidak dijangka. Takrifan komponen hendaklah kekal pada peringkat teratas. Jawapan yang hanya tertumpu pada kunci mungkin terlepas pepijat identiti yang berkaitan ini.

Langkah 7: Gunakan peraturan identiti yang sama pada senarai maya (virtualized lists).

Senarai maya berulang kali menggunakan semula sebilangan kecil slot yang kelihatan. Jika pustaka menerima itemKey, kembalikan ID entiti domain dan bukannya indeks tetingkap. Jika tidak, menatal atau menyusun semula boleh membenarkan satu entiti mewarisi keadaan daripada slot lain.

Untuk senarai yang besar, reka bentuk yang lebih selamat selalunya mengurangkan keadaan perniagaan yang tidak kekal di dalam baris. Simpan draf edit di atas senarai mengikut ID rekod dan biarkan setiap baris membaca draf entitinya. Kemudian pustaka pemayaan boleh menyahpasang baris di luar skrin tanpa memadam data perniagaan.

Langkah 8: Sahkan pemilikan keadaan, bukan sekadar teks yang dipaparkan.

Bagi setiap ujian, nyatakan ID yang sepatutnya memiliki keadaan tersebut:

OperasiHasil yang dijangkakan
Taip draf pada Alice, kemudian sisipkan Zoe di bahagian atasDraf masih milik Alice sahaja
Padam item di bahagian tengahKeadaan kembangan dan input baris lain tidak berpindah
Isih secara menurun, kemudian pulihkan susunan asalKeadaan setiap baris terus mengikut ID rekodnya
Tapis keluar Alice, kemudian pulihkan penapisKeadaan tempatan baris hilang selepas dinyahpasang; pulihkan daripada storan draf yang diangkat jika diperlukan
Cetuskan pemaparan semula induk dengan userId yang samaKeadaan tempatan borang dikekalkan
Bertukar daripada pengguna A kepada pengguna BBorang yang dikunci mengikut userId ditetapkan semula sepenuhnya
Gunakan kunci rawak buat sementara waktu dan cetuskan pemaparan semula indukKehilangan input dan penciptaan semula DOM menunjukkan mekanisme kegagalan
Terima ID penduaBerikan amaran atau tolak pada sempadan data untuk mengelakkan konflik kunci adik-beradik

Ujian juga perlu merangkumi fokus dan input tidak terkawal. Kunci yang salah mungkin menyebabkan keadaan React kelihatan munasabah sedangkan nilai DOM atau fokus yang dipegang oleh pelayar berpindah ke rekod yang salah. Kriteria penerimaan harus menerangkan kesinambungan entiti yang dapat dilihat oleh pengguna, bukan sekadar bilangan pemaparan.

Contoh Jawapan Berkualiti Tinggi

"Saya menganggap kunci sebagai sebahagian daripada identiti komponen, bukan sekadar atribut untuk membuang amaran konsol. React mengaitkan keadaan dengan kedudukan pokok pemaparan. Di bawah satu induk, jenis komponen dan kunci yang sama biasanya mewakili identiti yang sama, jadi kemas kini prop atau pergerakan boleh memaparkan semula komponen sambil mengekalkan keadaan. Jenis atau kunci yang diubah mewakili identiti baharu: React mengalih keluar subpokok lama dan memasang subpokok baharu.

Dalam senarai yang boleh diedit, indeks mengenal pasti kedudukan, bukan pengguna. Jika [Alice, Bob] menggunakan kunci 0 dan 1 dan Zoe disisipkan di bahagian atas, baris lama dengan kunci 0 kini boleh memaparkan Zoe, menyebabkan draf atau keadaan kembangan Alice muncul di bawah Zoe. Saya akan menggunakan user.id, yang memastikan identiti Alice stabil apabila dia berpindah daripada indeks 0 ke indeks 1. Kunci rawak, cap masa, atau UUID yang dijana semasa pemaparan tidak pernah sepadan dengan pemaparan sebelumnya, jadi React berulang kali mencipta semula komponen dan DOM serta kehilangan input. Kunci hanya perlu unik dalam kalangan adik-beradik, dan komponen anak tidak menerima key sebagai prop.

Borang butiran bergantung pada semantik produk. Jika pertukaran daripada pengguna A kepada pengguna B mesti mengosongkan keseluruhan borang, saya akan meletakkan key={userId} pada sempadan ProfileForm supaya semua keadaan keturunan ditetapkan semula bersama dan bukannya mengosongkan medan dalam beberapa Effect. Jika kembali kepada A mesti memulihkan draf, saya akan memastikan identiti diasingkan mengikut userId tetapi mengangkat atau mengekalkan draf secara luaran mengikut userId.

Akhir sekali, saya akan menguji penyisipan di bahagian atas, pemadaman, penyusunan semula, penapisan, pemaparan semula dengan ID yang sama, dan pertukaran dengan ID yang berbeza. Saya akan menyemak bahawa input, keadaan kembangan, fokus, dan draf semuanya mengikut ID domain yang dimaksudkan. Ini membuktikan bahawa kunci memodelkan identiti yang betul dan bukannya sekadar membenarkan halaman dikemas kini."

Kesilapan Lazim

  • Menerangkan kunci hanya sebagai pengoptimuman prestasi → Isu terasnya ialah identiti anak dan pemilikan keadaan → Terangkan ketidakpadanan keadaan terlebih dahulu, kemudian kos kemas kini.
  • Menggunakan indeks tatasusunan untuk setiap senarai → Penyisipan, pemadaman, penyusunan semula, dan penapisan mengubah pemetaan slot kepada entiti → Gunakan ID yang stabil daripada data.
  • Menjana UUID atau nilai rawak semasa pemaparan → Kunci berubah setiap kali dan mencipta semula komponen serta DOM → Jana dan simpan ID apabila item dicipta.
  • Memerlukan kunci menjadi unik secara global → React hanya memerlukan keunikan dalam kalangan adik-beradik → Nilaikan konflik dalam induk dan senarai semasa.
  • Membaca props.key dalam komponen anak → React tidak menghantar kunci sebagai prop biasa → Hantarkan rowId atau userId yang berasingan.
  • Mengosongkan borang yang kompleks medan demi medan dalam Effect → Ini memaparkan keadaan lapuk, menambah satu lagi pemaparan, dan boleh terlepas keadaan keturunan → Tukar kunci pada sempadan identiti yang betul.
  • Memasang semula keseluruhan halaman untuk mengosongkan satu medan → Ini menghilangkan fokus, kedudukan tatal, dan keadaan yang tidak berkaitan → Kecilkan sempadan kunci atau kemas kini medan secara eksplisit.
  • Menganggap kunci yang stabil mengekalkan keadaan selepas penapisan → Komponen yang ditapis mungkin telah dinyahpasang → Angkat atau kekalkan keadaan apabila pemulihan diperlukan.

Soalan Susulan

Susulan 1: Apakah kunci yang patut digunakan apabila data tidak mempunyai ID backend?

Jana ID, seperti UUID, dalam peristiwa yang mencipta rekod tempatan dan simpannya sebagai medan rekod. Setiap pemaparan kemudian membaca ID yang sama. Gabungan medan domain yang tidak boleh diubah dan unik sesama adik-beradik boleh digunakan jika kontrak tersebut terbukti. Jangan jana kunci secara sementara di dalam pemaparan map.

Susulan 2: Bilakah indeks tatasusunan boleh diterima sebagai kunci?

Ia agak selamat hanya apabila keahlian dan susunan kekal tetap sepanjang jangka hayat senarai, tiada penyisipan, pemadaman, penapisan, atau penyusunan semula, dan kedudukan itu sendiri ialah identiti. Paparan statik yang tetap mungkin memenuhi sempadan tersebut. Data perniagaan dinamik harus menggunakan ID entiti yang stabil kerana pengisihan atau penyuntingan serta-merta membatalkan andaian indeks tersebut.

Susulan 3: Jika hanya satu medan yang perlu dikosongkan apabila userId bertukar, patutkah kunci keseluruhan borang bertukar?

Tidak semestinya. Kunci menetapkan semula keseluruhan subpokok. Jika keadaan tempatan yang lain mesti kekal, gunakan medan terkawal, terbitkan nilai secara langsung daripada prop, atau laraskannya dalam laluan kemas kini data yang eksplisit. Pilih penetapan semula kunci hanya apabila keseluruhan subpokok mewakili entiti lain dan semua keadaan tempatannya harus bermula dari awal.

Susulan 4: Bagaimanakah draf boleh dipulihkan selepas menukar kunci?

Angkat draf ke induk, contohnya sebagai drafts[userId], atau kekalkannya secara luaran dengan peraturan tamat tempoh dan pembersihan. Kekalkan kunci borang berdasarkan userId supaya pengguna yang berbeza tidak berkongsi keadaan tempatan. Borang yang baru dipasang membaca nilai awalnya daripada draf tersimpan yang sepadan. Pengasingan identiti dan pengekalan draf adalah dua tanggungjawab yang berasingan.

Susulan 5: Adakah senarai maya masih memerlukan kunci yang stabil apabila ia sudah menggunakan semula DOM?

Ya. Pemayaan mengawal bilangan nod yang kelihatan; ia tidak mentakrifkan semula identiti domain. Jika pustaka mendedahkan itemKey, kembalikan ID rekod. Keadaan penyuntingan yang mesti bertahan selepas penyahpasangan juga harus berada di atas baris mengikut ID, jika tidak, ia tetap akan hilang apabila baris ditatal keluar dari tetingkap paparan.

Sumber awam

Soalan berkaitan