Saya memiliki array nilai true / false / undefined yang saya render sebagai daftar kotak centang.
Saat mengubah elemen array ke atau dari true, daftar kotak centang dirender ulang dengan kotak centang berikut (indeks + 1) yang mewarisi perubahan bersama dengan kotak centang yang diubah.
Kode:
{{#each range as |value idx|}}
<label><input type="checkbox" checked={{value}} {{action makeChange idx on="change"}}>{{idx}}: {{value}}</label><br/>
{{/each}}
Ketika saya menggunakan {{#each range key="@index" as |value idx|}}
itu bekerja dengan benar.
Twiddle: https://ember-twiddle.com/6d63548f35f99da19cee9f58fb64db59
@andrewtimberlake sepertinya menggunakan {{#each range key="@index" as |value idx|}}
dapat mengatasi masalah ini.
Tapi sepertinya bug, key
adalah untuk tujuan yang berbeda, https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/if?anchor= setiap
Saya rasa saya tahu apa yang terjadi di sini. Ini berantakan tapi saya akan mencoba menjelaskannya. Banyak kasus tepi (kesalahan pengguna garis batas) berkontribusi pada hal ini, dan saya tidak begitu yakin apa itu / bukan bug, apa dan bagaimana cara memperbaikinya.
Pertama-tama, saya perlu menjelaskan apa yang dilakukan parameter key
dalam {{#each}}
. TL; DR mencoba menentukan kapan dan apakah masuk akal untuk menggunakan kembali DOM yang ada, vs hanya membuat DOM dari awal.
Untuk tujuan kita, mari kita terima karena "DOM menyentuh" (misalnya memperbarui konten node teks, atribut, menambah atau menghapus konten, dll) mahal dan harus dihindari sebanyak mungkin.
Mari fokus pada bagian template yang agak sederhana:
<ul>
{{#each this.names as |name|}}
<li>{{name.first}} {{to-upper-case name.last}}</li>
{{/each}}
</ul>
Jika this.names
adalah ...
[
{ first: "Yehuda", last: "Katz" },
{ first: "Tom", last: "Dale" },
{ first: "Godfrey", last: "Chan" }
]
Maka Anda akan mendapatkan ...
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Sejauh ini bagus.
Sekarang bagaimana jika kita menambahkan { first: "Andrew", last: "Timberlake" }
ke daftar? Kami mengharapkan template untuk menghasilkan DOM berikut:
<ul>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
<li>Andrew TIMBERLAKE</li>
</ul>
Tapi bagaimana caranya_?
Cara paling naif untuk mengimplementasikan {{#each}}
helper adalah menghapus semua konten daftar setiap kali konten daftar berubah. Untuk melakukan ini, Anda perlu melakukan _ setidaknya_ 23 operasi:
<li>
node<li>
nodeto-upper-case
4 kaliIni sepertinya ... sangat tidak perlu dan mahal. Kita _ tahu_ tiga item pertama tidak berubah, jadi alangkah baiknya jika kita bisa melewatkan pekerjaan untuk baris tersebut.
Implementasi yang lebih baik adalah mencoba menggunakan kembali baris yang ada dan tidak melakukan pembaruan yang tidak perlu. Satu idenya adalah dengan mencocokkan baris dengan posisinya di templat. Pada dasarnya inilah yang dilakukan key="@index"
:
{ first: "Yehuda", last: "Katz" }
dengan baris pertama, <li>Yehuda KATZ</li>
:to-upper-case
pembantu, dan oleh karena itu kita tahu keluaran dari pembantu itu ("KATZ" ) _also_ tidak berubah, jadi tidak ada yang bisa dilakukan di sini<li>
to-upper-case
("Timberlake" -> "TIMBERLAKE")Jadi, dengan penerapan ini, kami mengurangi jumlah total operasi dari 23 menjadi 5 (👋 mengabaikan biaya perbandingan, tetapi untuk tujuan kami, kami mengasumsikan mereka relatif murah dibandingkan dengan yang lain). Tidak buruk.
Tapi sekarang, apa yang akan terjadi jika, alih-alih _appending_ { first: "Andrew", last: "Timberlake" }
ke daftar, kita _menambahkan_ sebagai gantinya? Kami mengharapkan template untuk menghasilkan DOM berikut:
<ul>
<li>Andrew TIMBERLAKE</li>
<li>Yehuda KATZ</li>
<li>Tom DALE</li>
<li>Godfrey CHAN</li>
</ul>
Tapi bagaimana caranya_?
{ first: "Andrew", last: "Timberlake" }
dengan baris pertama, <li>Yehuda KATZ</li>
:to-upper-case
{ first: "Yehuda", last: "Katz" }
dengan baris kedua, <li>Tom DALE</li>
, 3 operasi lainnya{ first: "Tom", last: "Dale" }
dengan baris kedua, <li>Godfrey CHAN</li>
, 3 operasi lainnya<li>
to-upper-case
("Chan" -> "CHAN")Itu 14 operasi. Aduh!
Tampaknya tidak perlu, karena secara konseptual, apakah kita melakukan prepending atau appending, kita masih hanya mengubah (menyisipkan) satu objek dalam array. Secara optimal, kami harus dapat menangani kasus ini seperti yang kami lakukan dalam skenario append.
Di sinilah key="@identity"
masuk. Daripada mengandalkan _order_ elemen dalam larik, kami menggunakan identitas objek JavaScript mereka ( ===
):
===
) objek pertama { first: "Andrew", last: "Timberlake" }
. Karena tidak ada yang ditemukan, sisipkan (awali) baris baru:<li>
to-upper-case
("Timberlake" -> "TIMBERLAKE")===
) objek kedua { first: "Yehuda", last: "Katz" }
. Menemukan <li>Yehuda KATZ</li>
:to-upper-case
pembantu, dan oleh karena itu kita tahu keluaran dari pembantu itu ("KATZ" ) _also_ tidak berubah, jadi tidak ada yang bisa dilakukan di siniDengan itu, kami kembali ke 5 operasi optimal.
Sekali lagi ini adalah mengabaikan perbandingan dan biaya pembukuan. Memang, itu juga tidak gratis, dan dalam contoh yang sangat sederhana ini, mungkin tidak sepadan. Tapi bayangkan daftarnya besar dan setiap baris memanggil komponen yang rumit (dengan banyak pembantu, properti yang dihitung, sub-komponen, dll). Bayangkan feed berita LinkedIn, misalnya. Jika kami tidak mencocokkan baris yang tepat dengan data yang benar, argumen komponen Anda berpotensi banyak berubah dan menyebabkan lebih banyak pembaruan DOM daripada yang Anda perkirakan. Ada juga masalah dengan pencocokan elemen DOM yang salah dan kehilangan status DOM, seperti posisi kursor dan status pemilihan teks.
Secara keseluruhan, perbandingan ekstra dan biaya pembukuan dengan mudah bernilai sebagian besar waktu di aplikasi dunia nyata. Karena key="@identity"
adalah default di Ember dan berfungsi dengan baik untuk hampir semua kasus, Anda biasanya tidak perlu khawatir tentang menyetel argumen key
saat menggunakan {{#each}}
.
Tapi tunggu, ada masalah. Bagaimana dengan kasus ini?
const YEHUDA = { first: "Yehuda", last: "Katz" };
const TOM = { first: "Tom", last: "Dale" };
const GODFREY = { first: "Godfrey", last: "Chan" };
this.list = [
YEHUDA,
TOM,
GODFREY,
TOM, // duplicate
YEHUDA, // duplicate
YEHUDA, // duplicate
YEHUDA // duplicate
];
Masalahnya di sini adalah bahwa objek yang sama _bisa_ muncul beberapa kali dalam daftar yang sama. Ini merusak algoritme naif @identity
, khususnya bagian di mana kami mengatakan "Temukan baris yang ada yang datanya cocok ( ===
) ..." - ini hanya berfungsi jika data ke hubungan DOM adalah 1 : 1, yang tidak benar dalam kasus ini. Ini mungkin tampak tidak mungkin dalam praktiknya, tetapi sebagai kerangka kerja, kami harus menanganinya.
Untuk menghindari ini, kami menggunakan semacam pendekatan hybrid untuk menangani tabrakan ini. Secara internal, pemetaan kunci-ke-DOM terlihat seperti ini:
"YEHUDA" => <li>Yehuda...</li>
"TOM" => <li>Tom...</li>
"GODFREY" => <li>Godfrey...</li>
"TOM-1" => <li>Tom...</li>
"YEHUDA-1" => <li>Yehuda...</li>
"YEHUDA-2" => <li>Yehuda...</li>
"YEHUDA-3" => <li>Yehuda...</li>
Untuk sebagian besar, ini _is_ cukup langka, dan jika muncul, ini berfungsi dengan Baik ™ hampir sepanjang waktu. Jika, karena alasan tertentu, ini tidak berhasil, Anda selalu dapat menggunakan jalur kunci (atau mekanisme penguncian yang lebih canggih di RFC 321 ).
Setelah semua pembicaraan itu, kita sekarang siap untuk melihat skenario di Twiddle.
Pada dasarnya, kami mulai dengan daftar ini: [undefined, undefined, undefined, undefined, undefined]
.
Catatan tidak terkait:
Array(5)
adalah _not_ sama dengan[undefined, undefined, undefined, undefined, undefined]
. Ini menghasilkan "array berlubang" yang merupakan sesuatu yang harus Anda hindari secara umum. Namun, ini tidak terkait dengan bug ini, karena ketika mengakses "lubang" Anda memang mendapatkan kembaliundefined
. Jadi untuk tujuan _sangat sempit_ kita saja, keduanya sama.
Karena kami tidak menentukan kuncinya, Ember menggunakan @identity
secara default. Selanjutnya, karena ini adalah tabrakan, kami berakhir dengan sesuatu seperti ini:
"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...
Sekarang, katakanlah kita mengklik kotak centang pertama:
{{action}}
dan dikirim ulang ke metode makeChange
[true, undefined, undefined, undefined, undefined]
.Bagaimana DOM diperbarui?
===
) objek pertama true
. Karena tidak ada yang ditemukan, sisipkan (awali) baris baru <input checked=true ...>0: true...
===
) objek kedua undefined
. Menemukan <input ...>0: ...
(sebelumnya baris PERTAMA):{{idx}}
menjadi 1
===
) objek ketiga undefined
. Karena ini adalah kedua kalinya kami melihat undefined
, kunci internal adalah undefined-1
, jadi kami menemukan <input ...>1: ...
(sebelumnya baris KEDUA):{{idx}}
menjadi 2
undefined-2
dan undefined-3
undefined-4
(karena ada satu baris undefined
dalam larik setelah pembaruan)Jadi ini menjelaskan bagaimana kami mendapatkan hasil yang Anda miliki di twiddle. Pada dasarnya semua baris DOM bergeser ke bawah satu, dan yang baru disisipkan di atas, sedangkan {{idx}}
diperbarui untuk sisanya.
Bagian yang sangat tidak terduga adalah 2.2. Meskipun kotak centang pertama (yang diklik) digeser satu baris ke posisi kedua, Anda mungkin mengira Ember ke properti checked
telah berubah menjadi true
, dan karena nilai terikatnya tidak ditentukan, Anda mungkin berharap Ember mengubahnya kembali ke false
, sehingga tidak mencentangnya.
Tapi ini bukan cara kerjanya. Seperti disebutkan di awal, mengakses DOM itu mahal. Ini termasuk _reading_ dari DOM. Jika, pada setiap pembaruan, kami harus membaca nilai terbaru dari DOM untuk perbandingan kami, itu akan sangat menggagalkan tujuan pengoptimalan kami. Oleh karena itu, untuk menghindarinya, kami mengingat nilai terakhir yang telah kami tulis ke DOM, dan membandingkan nilai saat ini dengan nilai yang di-cache tanpa harus membacanya kembali dari DOM. Hanya jika ada perbedaan kita menulis nilai baru ke DOM (dan menyimpannya dalam cache untuk waktu berikutnya). Ini adalah pengertian di mana kami berbagi pendekatan "DOM virtual" yang sama, tetapi kami hanya melakukannya di simpul daun, bukan memvirtualisasikan "struktur pohon" dari seluruh DOM.
Jadi, TL; DR, "mengikat" properti checked
(atau properti value
dari bidang teks, dll) tidak benar-benar berfungsi seperti yang Anda harapkan. Bayangkan jika Anda merender <div>{{this.name}}</div>
dan secara manual memperbarui textContent
dari elemen div
menggunakan jQuery
atau dengan chrome inspector. Anda tidak akan mengharapkan Ember memperhatikan itu dan memperbarui this.name
untuk Anda. Ini pada dasarnya adalah hal yang sama: karena pembaruan ke properti checked
terjadi di luar Ember (melalui perilaku default browser untuk kotak centang), Ember tidak akan tahu tentang itu.
Inilah mengapa pembantu {{input}}
ada. Ia harus mendaftarkan event listener yang relevan pada elemen HTML yang mendasarinya dan mencerminkan operasi ke dalam perubahan properti yang sesuai, sehingga pihak yang berkepentingan (misalnya lapisan rendering) dapat diberitahukan.
Saya tidak yakin di mana itu meninggalkan kita. Saya mengerti mengapa ini mengejutkan, tetapi saya cenderung mengatakan ini adalah serangkaian kesalahan pengguna yang tidak menguntungkan. Mungkin kita harus melawan pengikatan properti ini pada elemen masukan?
Kode yang relevan:
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L390-L391
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L436-L445
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L451-L466
@chancode - terima kasih atas penjelasannya yang luar biasa. Apakah itu berarti <input ... >
tidak boleh digunakan tetapi hanya {{input ...}}
untuk mencegah semua kesalahan seperti itu?
@ boris-petrov mungkin ada beberapa kasus terbatas yang dapat diterima .. seperti bidang teks yang hanya bisa dibaca untuk "salin url ini ke papan klip Anda", atau Anda _dapat_ menggunakan elemen masukan + {{action}}
untuk mencegat DOM dan mencerminkan pembaruan properti secara manual (yang coba dilakukan twiddle, kecuali itu juga mengalami tabrakan @identity
), tetapi ya pada titik tertentu Anda baru saja mengimplementasikan kembali {{input}}
dan menangani semua kasus tepi yang sudah ditangani untuk Anda. Jadi menurut saya _mungkin_ adil untuk mengatakan bahwa Anda sebaiknya menggunakan {{input}}
sebagian besar, jika tidak semua, dari waktu ke waktu.
Namun, hal itu tetap tidak akan "memperbaiki" kasus ini di mana terdapat benturan dengan kunci. Lihat https://ember-twiddle.com/0f2369021128e2ae0c445155df5bb034?openFiles=templates.application.hbs%2C
Itulah sebabnya saya berkata, saya 100% tidak yakin apa yang harus saya lakukan. Di satu sisi saya setuju ini mengejutkan dan tidak terduga, di sisi lain, tabrakan semacam ini cukup jarang terjadi di aplikasi nyata dan itulah mengapa argumen "kunci" dapat disesuaikan (ini adalah kasus di mana kunci "@identity" default fungsinya tidak Cukup Baik ™, itulah sebabnya fitur itu ada).
@chancode - ini mengingatkan saya pada masalah lain yang saya buka beberapa waktu lalu . Apakah menurut Anda ada yang serupa di sana? Jawaban yang saya terima di sana (tentang perlunya menggunakan replace
daripada set
saat mengatur elemen array) masih terasa aneh bagi saya.
@ boris-petrov Saya rasa itu tidak ada hubungannya
hai, kami menggunakan sortablejs untuk daftar draggable dengan ember. tolong periksa demo ini untuk mereproduksi setiap masalah.
langkah:
Anda dapat melihat item yang diseret tetap berada di pohon dom.
tetapi, jika menyeret item ke posisi lain (bukan item terakhir), tampaknya berfungsi dengan baik.
Komentar yang paling membantu
Saya rasa saya tahu apa yang terjadi di sini. Ini berantakan tapi saya akan mencoba menjelaskannya. Banyak kasus tepi (kesalahan pengguna garis batas) berkontribusi pada hal ini, dan saya tidak begitu yakin apa itu / bukan bug, apa dan bagaimana cara memperbaikinya.
Mayor 🔑
Pertama-tama, saya perlu menjelaskan apa yang dilakukan parameter
key
dalam{{#each}}
. TL; DR mencoba menentukan kapan dan apakah masuk akal untuk menggunakan kembali DOM yang ada, vs hanya membuat DOM dari awal.Untuk tujuan kita, mari kita terima karena "DOM menyentuh" (misalnya memperbarui konten node teks, atribut, menambah atau menghapus konten, dll) mahal dan harus dihindari sebanyak mungkin.
Mari fokus pada bagian template yang agak sederhana:
Jika
this.names
adalah ...Maka Anda akan mendapatkan ...
Sejauh ini bagus.
Menambahkan item ke daftar
Sekarang bagaimana jika kita menambahkan
{ first: "Andrew", last: "Timberlake" }
ke daftar? Kami mengharapkan template untuk menghasilkan DOM berikut:Tapi bagaimana caranya_?
Cara paling naif untuk mengimplementasikan
{{#each}}
helper adalah menghapus semua konten daftar setiap kali konten daftar berubah. Untuk melakukan ini, Anda perlu melakukan _ setidaknya_ 23 operasi:<li>
node<li>
nodeto-upper-case
4 kaliIni sepertinya ... sangat tidak perlu dan mahal. Kita _ tahu_ tiga item pertama tidak berubah, jadi alangkah baiknya jika kita bisa melewatkan pekerjaan untuk baris tersebut.
🔑 @index
Implementasi yang lebih baik adalah mencoba menggunakan kembali baris yang ada dan tidak melakukan pembaruan yang tidak perlu. Satu idenya adalah dengan mencocokkan baris dengan posisinya di templat. Pada dasarnya inilah yang dilakukan
key="@index"
:{ first: "Yehuda", last: "Katz" }
dengan baris pertama,<li>Yehuda KATZ</li>
:1.1. "Yehuda" === "Yehuda", tidak ada yang bisa dilakukan
1.2. (spasi tidak berisi data dinamis jadi tidak perlu perbandingan)
1.3. "Katz" === "Katz", karena pembantu itu "murni", kita tahu kita tidak perlu memanggil kembali
to-upper-case
pembantu, dan oleh karena itu kita tahu keluaran dari pembantu itu ("KATZ" ) _also_ tidak berubah, jadi tidak ada yang bisa dilakukan di sini3.1. Masukkan simpul
<li>
3.2. Masukkan simpul teks ("Andrew")
3.3. Masukkan node teks (spasi)
3.4. Panggil pembantu
to-upper-case
("Timberlake" -> "TIMBERLAKE")3.5. Masukkan node teks ("TIMBERLAKE")
Jadi, dengan penerapan ini, kami mengurangi jumlah total operasi dari 23 menjadi 5 (👋 mengabaikan biaya perbandingan, tetapi untuk tujuan kami, kami mengasumsikan mereka relatif murah dibandingkan dengan yang lain). Tidak buruk.
Mempersiapkan item ke daftar
Tapi sekarang, apa yang akan terjadi jika, alih-alih _appending_
{ first: "Andrew", last: "Timberlake" }
ke daftar, kita _menambahkan_ sebagai gantinya? Kami mengharapkan template untuk menghasilkan DOM berikut:Tapi bagaimana caranya_?
{ first: "Andrew", last: "Timberlake" }
dengan baris pertama,<li>Yehuda KATZ</li>
:1.1. "Andrew"! == "Yehuda", perbarui simpul teks
1.2. (spasi tidak berisi data dinamis jadi tidak perlu perbandingan)
1.3. "Timberlake"! == "Katz", cabut kembali pembantu
to-upper-case
1.4. Perbarui node teks dari "KATZ" menjadi "TIMBERLAKE"
{ first: "Yehuda", last: "Katz" }
dengan baris kedua,<li>Tom DALE</li>
, 3 operasi lainnya{ first: "Tom", last: "Dale" }
dengan baris kedua,<li>Godfrey CHAN</li>
, 3 operasi lainnya3.1. Masukkan simpul
<li>
3.2. Masukkan simpul teks ("Godfrey")
3.3. Masukkan node teks (spasi)
3.4. Panggil pembantu
to-upper-case
("Chan" -> "CHAN")3.5. Masukkan node teks ("CHAN")
Itu 14 operasi. Aduh!
🔑 @ identitas
Tampaknya tidak perlu, karena secara konseptual, apakah kita melakukan prepending atau appending, kita masih hanya mengubah (menyisipkan) satu objek dalam array. Secara optimal, kami harus dapat menangani kasus ini seperti yang kami lakukan dalam skenario append.
Di sinilah
key="@identity"
masuk. Daripada mengandalkan _order_ elemen dalam larik, kami menggunakan identitas objek JavaScript mereka (===
):===
) objek pertama{ first: "Andrew", last: "Timberlake" }
. Karena tidak ada yang ditemukan, sisipkan (awali) baris baru:1.1. Masukkan node
<li>
1.2. Masukkan simpul teks ("Andrew")
1.3. Masukkan node teks (spasi)
1.4. Panggil pembantu
to-upper-case
("Timberlake" -> "TIMBERLAKE")1.5. Masukkan node teks ("TIMBERLAKE")
===
) objek kedua{ first: "Yehuda", last: "Katz" }
. Menemukan<li>Yehuda KATZ</li>
:2.1. "Yehuda" === "Yehuda", tidak ada yang bisa dilakukan
2.2. (spasi tidak berisi data dinamis jadi tidak perlu perbandingan)
2.3. "Katz" === "Katz", karena pembantu itu "murni", kita tahu kita tidak perlu memanggil kembali
to-upper-case
pembantu, dan oleh karena itu kita tahu keluaran dari pembantu itu ("KATZ" ) _also_ tidak berubah, jadi tidak ada yang bisa dilakukan di siniDengan itu, kami kembali ke 5 operasi optimal.
Scaling Up
Sekali lagi ini adalah mengabaikan perbandingan dan biaya pembukuan. Memang, itu juga tidak gratis, dan dalam contoh yang sangat sederhana ini, mungkin tidak sepadan. Tapi bayangkan daftarnya besar dan setiap baris memanggil komponen yang rumit (dengan banyak pembantu, properti yang dihitung, sub-komponen, dll). Bayangkan feed berita LinkedIn, misalnya. Jika kami tidak mencocokkan baris yang tepat dengan data yang benar, argumen komponen Anda berpotensi banyak berubah dan menyebabkan lebih banyak pembaruan DOM daripada yang Anda perkirakan. Ada juga masalah dengan pencocokan elemen DOM yang salah dan kehilangan status DOM, seperti posisi kursor dan status pemilihan teks.
Secara keseluruhan, perbandingan ekstra dan biaya pembukuan dengan mudah bernilai sebagian besar waktu di aplikasi dunia nyata. Karena
key="@identity"
adalah default di Ember dan berfungsi dengan baik untuk hampir semua kasus, Anda biasanya tidak perlu khawatir tentang menyetel argumenkey
saat menggunakan{{#each}}
.Tabrakan 💥
Tapi tunggu, ada masalah. Bagaimana dengan kasus ini?
Masalahnya di sini adalah bahwa objek yang sama _bisa_ muncul beberapa kali dalam daftar yang sama. Ini merusak algoritme naif
@identity
, khususnya bagian di mana kami mengatakan "Temukan baris yang ada yang datanya cocok (===
) ..." - ini hanya berfungsi jika data ke hubungan DOM adalah 1 : 1, yang tidak benar dalam kasus ini. Ini mungkin tampak tidak mungkin dalam praktiknya, tetapi sebagai kerangka kerja, kami harus menanganinya.Untuk menghindari ini, kami menggunakan semacam pendekatan hybrid untuk menangani tabrakan ini. Secara internal, pemetaan kunci-ke-DOM terlihat seperti ini:
Untuk sebagian besar, ini _is_ cukup langka, dan jika muncul, ini berfungsi dengan Baik ™ hampir sepanjang waktu. Jika, karena alasan tertentu, ini tidak berhasil, Anda selalu dapat menggunakan jalur kunci (atau mekanisme penguncian yang lebih canggih di RFC 321 ).
Kembali ke "🐛"
Setelah semua pembicaraan itu, kita sekarang siap untuk melihat skenario di Twiddle.
Pada dasarnya, kami mulai dengan daftar ini:
[undefined, undefined, undefined, undefined, undefined]
.Karena kami tidak menentukan kuncinya, Ember menggunakan
@identity
secara default. Selanjutnya, karena ini adalah tabrakan, kami berakhir dengan sesuatu seperti ini:Sekarang, katakanlah kita mengklik kotak centang pertama:
{{action}}
dan dikirim ulang ke metodemakeChange
[true, undefined, undefined, undefined, undefined]
.Bagaimana DOM diperbarui?
===
) objek pertamatrue
. Karena tidak ada yang ditemukan, sisipkan (awali) baris baru<input checked=true ...>0: true...
===
) objek keduaundefined
. Menemukan<input ...>0: ...
(sebelumnya baris PERTAMA):2.1. Perbarui simpul teks
{{idx}}
menjadi1
2.2. Jika tidak, sejauh yang bisa dikatakan Ember, tidak ada lagi yang berubah di baris ini, tidak ada yang bisa dilakukan
===
) objek ketigaundefined
. Karena ini adalah kedua kalinya kami melihatundefined
, kunci internal adalahundefined-1
, jadi kami menemukan<input ...>1: ...
(sebelumnya baris KEDUA):3.1. Perbarui simpul teks
{{idx}}
menjadi2
3.2. Jika tidak, sejauh yang bisa dikatakan Ember, tidak ada lagi yang berubah di baris ini, tidak ada yang bisa dilakukan
undefined-2
danundefined-3
undefined-4
(karena ada satu barisundefined
dalam larik setelah pembaruan)Jadi ini menjelaskan bagaimana kami mendapatkan hasil yang Anda miliki di twiddle. Pada dasarnya semua baris DOM bergeser ke bawah satu, dan yang baru disisipkan di atas, sedangkan
{{idx}}
diperbarui untuk sisanya.Bagian yang sangat tidak terduga adalah 2.2. Meskipun kotak centang pertama (yang diklik) digeser satu baris ke posisi kedua, Anda mungkin mengira Ember ke properti
checked
telah berubah menjaditrue
, dan karena nilai terikatnya tidak ditentukan, Anda mungkin berharap Ember mengubahnya kembali kefalse
, sehingga tidak mencentangnya.Tapi ini bukan cara kerjanya. Seperti disebutkan di awal, mengakses DOM itu mahal. Ini termasuk _reading_ dari DOM. Jika, pada setiap pembaruan, kami harus membaca nilai terbaru dari DOM untuk perbandingan kami, itu akan sangat menggagalkan tujuan pengoptimalan kami. Oleh karena itu, untuk menghindarinya, kami mengingat nilai terakhir yang telah kami tulis ke DOM, dan membandingkan nilai saat ini dengan nilai yang di-cache tanpa harus membacanya kembali dari DOM. Hanya jika ada perbedaan kita menulis nilai baru ke DOM (dan menyimpannya dalam cache untuk waktu berikutnya). Ini adalah pengertian di mana kami berbagi pendekatan "DOM virtual" yang sama, tetapi kami hanya melakukannya di simpul daun, bukan memvirtualisasikan "struktur pohon" dari seluruh DOM.
Jadi, TL; DR, "mengikat" properti
checked
(atau propertivalue
dari bidang teks, dll) tidak benar-benar berfungsi seperti yang Anda harapkan. Bayangkan jika Anda merender<div>{{this.name}}</div>
dan secara manual memperbaruitextContent
dari elemendiv
menggunakanjQuery
atau dengan chrome inspector. Anda tidak akan mengharapkan Ember memperhatikan itu dan memperbaruithis.name
untuk Anda. Ini pada dasarnya adalah hal yang sama: karena pembaruan ke propertichecked
terjadi di luar Ember (melalui perilaku default browser untuk kotak centang), Ember tidak akan tahu tentang itu.Inilah mengapa pembantu
{{input}}
ada. Ia harus mendaftarkan event listener yang relevan pada elemen HTML yang mendasarinya dan mencerminkan operasi ke dalam perubahan properti yang sesuai, sehingga pihak yang berkepentingan (misalnya lapisan rendering) dapat diberitahukan.Saya tidak yakin di mana itu meninggalkan kita. Saya mengerti mengapa ini mengejutkan, tetapi saya cenderung mengatakan ini adalah serangkaian kesalahan pengguna yang tidak menguntungkan. Mungkin kita harus melawan pengikatan properti ini pada elemen masukan?