Bagaimana memahami dan menguasai esensi DOM virtual? Saya menyarankan semua orang untuk mempelajari proyek Snabbdom.
Snabbdom adalah pustaka implementasi DOM virtual. Alasan rekomendasinya adalah: pertama, kodenya relatif kecil, dan kode intinya hanya beberapa ratus baris, kedua, Vue memanfaatkan ide proyek ini untuk mengimplementasikan DOM virtual; ide desain/implementasi dan perluasan proyek ini Layak untuk referensi Anda.
snabb /snab/, bahasa Swedia, artinya cepat.
Sesuaikan postur duduk Anda yang nyaman dan semangat. Mari kita mulai. Untuk mempelajari DOM virtual, pertama-tama kita harus mengetahui pengetahuan dasar DOM dan kelemahan mengoperasikan DOM secara langsung dengan JS.
(Document Object Model) adalah model objek dokumen yang menggunakan struktur pohon objek untuk mewakili dokumen HTML/XML. Ujung setiap cabang pohon adalah sebuah node yang berisi objek. Metode DOM API memungkinkan Anda memanipulasi pohon ini dengan cara tertentu. Dengan metode ini, Anda dapat mengubah struktur, gaya, atau konten dokumen.
Semua node di pohon DOM adalah Node
pertama, Node
adalah kelas dasar. Element
, Text
, dan Comment
semuanya mewarisinya.
Dengan kata lain, Element
, Text
dan Comment
adalah tiga Node
khusus, yang masing-masing disebut ELEMENT_NODE
.
TEXT_NODE
dan COMMENT_NODE
mewakili node elemen (tag HTML), node teks, dan node komentar. Element
juga memiliki subkelas yang disebut HTMLElement
. Apa perbedaan antara HTMLElement
dan Element
? HTMLElement
mewakili elemen dalam HTML, seperti: <span>
, <img>
, dll., dan beberapa elemen bukan standar HTML, seperti <svg>
. Anda dapat menggunakan metode berikut untuk menentukan apakah elemen ini HTMLElement
:
document.getElementById('myIMG') instanceof HTMLElement
Itu "mahal" bagi browser untuk membuat DOM. Mari kita ambil contoh klasik. Kita dapat membuat elemen p sederhana melalui document.createElement('p')
dan mencetak semua atributnya:
Anda dapat melihat bahwa ada banyak atribut yang dicetak. Jika sering memperbarui pohon DOM yang kompleks, masalah kinerja akan terjadi. Virtual DOM menggunakan objek JS asli untuk mendeskripsikan node DOM, sehingga membuat objek JS jauh lebih murah dibandingkan membuat objek DOM.
VNode adalah struktur objek yang menggambarkan DOM virtual di Snabbdom. Isinya adalah sebagai berikut:
ketik Kunci = string nomor |. antarmuka VNode { // Pemilih CSS, seperti: 'p#container'. sel: string |.tidak terdefinisi; // Memanipulasi kelas CSS, atribut, dll. melalui modul. data: VNodeData |. // Array simpul anak virtual, elemen array juga bisa berupa string. anak-anak: Array<VNode |.string> |. // Menunjuk ke objek DOM asli yang dibuat. elm: Simpul |.tidak terdefinisi; /** * Ada dua situasi untuk atribut teks: * 1. Pemilih sel tidak disetel, menunjukkan bahwa node itu sendiri adalah node teks. * 2. sel disetel, menunjukkan bahwa konten node ini adalah node teks. */ teks: string |.tidak terdefinisi; // Digunakan untuk memberikan pengidentifikasi untuk DOM yang ada, yang harus unik di antara elemen saudaranya agar secara efektif menghindari operasi rekonstruksi yang tidak diperlukan. kunci: Kunci |. tidak terdefinisi; } // Beberapa pengaturan pada vnode.data, kait fungsi kelas atau siklus hidup, dll. antarmuka VNodeData { alat peraga?: Alat peraga; attrs?: Attrs; kelas?: Kelas; gaya?: VNodeStyle; kumpulan data?: Kumpulan data; aktif?: Aktif; lampirkanData?: LampirkanData; kait?: Kait; kunci?: Kunci; ns?: string; // untuk SVG fn?: () => VNode; // untuk terima kasih args?: apa saja[]; // untuk terima kasih adalah?: string; // untuk elemen khusus v1 [kunci: string]: apa saja; // untuk modul pihak ketiga lainnya }
Misalnya mendefinisikan objek vnode seperti ini:
const vnode = h( 'p#wadah', { kelas: { aktif: benar } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Ini tebal'), 'dan ini hanya teks biasa' ]);
Kami membuat objek vnode melalui fungsi h(sel, b, c)
. Implementasi kode h()
terutama menentukan apakah parameter b dan c ada, dan memprosesnya menjadi data dan turunan pada akhirnya akan berbentuk array. Terakhir, format tipe VNode
yang ditentukan di atas dikembalikan melalui fungsi vnode()
.
Pertama mari kita ambil contoh diagram sederhana dari proses yang sedang berjalan, dan pertama-tama kita mempunyai konsep proses umum:
Pemrosesan diff adalah proses yang digunakan untuk menghitung perbedaan antara node baru dan lama.
Mari kita lihat contoh kode yang dijalankan oleh Snabbdom:
import { inisiasi, modul kelas, alat peragaModul, gayaModul, acaraListenersModule, H, } dari 'snabbdom'; const tambalan = init([ // Inisialisasi fungsi patch classModule dengan meneruskan modul, // Aktifkan fungsi kelas propsModule, // Mendukung penerusan props styleModule, // Mendukung gaya inline dan animasi eventListenersModule, // Menambahkan event listening]); // <p id="wadah"></p> const container = dokumen.getElementById('container'); const vnode = h( 'p#container.two.classes', { pada: { klik: someFn } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Ini tebal'), 'dan ini hanya teks biasa', h('a', { props: { href: '/foo' } }, "Aku akan mengantarmu ke berbagai tempat!"), ] ); // Masukkan node elemen yang kosong. tambalan(wadah, vnode); const Vnode baru = h( 'p#container.two.classes', { pada: { klik: anotherEventHandler } }, [ H( 'menjangkau', { gaya: { fontWeight: 'normal', fontStyle: 'italic' } }, 'Ini sekarang tipe miring' ), 'dan ini masih teks biasa', h('a', { props: { href: ''/bar' } }, "Saya akan mengantarmu ke berbagai tempat!"), ] ); // Panggil patch() lagi untuk memperbarui node lama ke node baru. patch(vnode, newVnode);
Seperti yang dapat dilihat dari diagram proses dan kode contoh, proses berjalan Snabbdom dijelaskan sebagai berikut:
panggilan pertama init()
untuk inisialisasi, dan modul yang akan digunakan perlu dikonfigurasi selama inisialisasi. Misalnya, modul classModule
digunakan untuk mengonfigurasi atribut kelas elemen dalam bentuk objek; modul eventListenersModule
digunakan untuk mengonfigurasi pendengar acara, dll. Fungsi patch()
akan dikembalikan setelah init()
dipanggil.
Buat objek vnode yang diinisialisasi melalui fungsi h()
, panggil fungsi patch()
untuk memperbaruinya, dan terakhir buat objek DOM sebenarnya melalui createElm()
.
Jika pembaruan diperlukan, buat objek vnode baru, panggil fungsi patch()
untuk memperbarui, dan selesaikan pembaruan diferensial node ini dan node turunan melalui patchVnode()
dan updateChildren()
.
Snabbdom menggunakan desain modul untuk memperluas pembaruan properti terkait alih-alih menulis semuanya ke dalam kode inti. Jadi bagaimana hal ini dirancang dan diimplementasikan? Selanjutnya, mari kita bahas inti desain Kangkang, Hooks—fungsi siklus hidup.
Snabbdom menyediakan serangkaian fungsi siklus hidup yang kaya, juga dikenal sebagai fungsi hook. Fungsi siklus hidup ini dapat diterapkan dalam modul atau dapat didefinisikan langsung di vnode. Misalnya, kita dapat mendefinisikan eksekusi hook pada vnode seperti ini:
h('p.row', { kunci: 'baris saya', kait: { masukkan: (vnode) => { console.log(vnode.elm.offsetHeight); }, }, });
Semua fungsi siklus hidup dideklarasikan sebagai berikut:
nama | trigger node | parameter panggilan balik |
---|---|---|
pre | patch mulai eksekusi | tidak ada |
init | vnode yang ditambahkan | vnode |
create | elemen DOM berdasarkan vnode dibuat | emptyVnode, vnode |
insert | vnode dimasukkan ke | vnode |
prepatch | vnode DOM adalah hendak menambal | oldVnode, vnode |
update | vnode telah diperbarui | oldVnode, vnode |
postpatch | telah ditambal | oldVnode, vnode |
destroy | vnode telah dihapus secara langsung atau tidak | vnode |
remove | telah menghapus vnode dari DOM | vnode, removeCallback |
post | deleteCallback telah menyelesaikan proses patch | tidak ada |
yang berlaku ke modul: pre
, create
, update
, destroy
, remove
, post
. Yang berlaku untuk deklarasi vnode adalah: init
, create
, insert
, prepatch
, update
, postpatch
, destroy
, remove
.
Mari kita lihat bagaimana Kangkang diimplementasikan. Sebagai contoh, mari kita ambil modul classModule
sebagai contoh pernyataan Kangkang:
import { VNode, VNodeData } from "../vnode"; impor { Modul } dari "./module"; tipe ekspor Kelas = Rekam<string, boolean>; fungsi updateClass(oldVnode: VNode, vnode: VNode): void { // Berikut detail pembaruan atribut kelas, abaikan saja untuk saat ini. // ... } ekspor const classModule: Module = { create: updateClass, update: updateClass };
Module
dapat melihat bahwa definisi modul yang terakhir diekspor adalah sebuah objek. Kunci dari objek tersebut adalah nama fungsi kait sebagai berikut:
impor { Pra-kait, Buat Kait, PembaruanHook, HancurkanHook, Hapus Kait, Pasca Kait, } dari "../kait"; tipe ekspor Modul = Parsial<{ pra: PraHook; buat: BuatHook; pembaruan: UpdateHook; menghancurkan: HancurkanHook; hapus: HapusHook; pos: PostHook; }>;
Partial
di TS berarti atribut setiap kunci dalam objek bisa kosong. Artinya, cukup tentukan hook mana yang Anda pedulikan dalam definisi modul. Sekarang hook telah ditentukan, bagaimana cara mengeksekusinya dalam prosesnya? Selanjutnya mari kita lihat fungsi init()
:
// Hook apa saja yang dapat didefinisikan dalam modul. kait const: Array<keyof Module> = [ "membuat", "memperbarui", "menghapus", "menghancurkan", "pra", "pos", ]; fungsi ekspor init( modul: Array<Parsial<Modul>>, domApi?: DOMAPI, pilihan?: Pilihan ) { //Fungsi hook yang didefinisikan dalam modul akhirnya akan disimpan di sini. const cbs: ModuleHooks = { membuat: [], memperbarui: [], menghapus: [], menghancurkan: [], pra: [], pos: [], }; // ... // Lintasi hook yang ditentukan dalam modul dan simpan bersama-sama. for (const kait kait) { for (modul const dari modul) { const currentHook = modul[hook]; if (currentHook !== tidak terdefinisi) { (cbs[hook] seperti apa pun[]).push(currentHook); } } } // ... }
Anda dapat melihat bahwa init()
pertama-tama melintasi setiap modul selama eksekusi, dan kemudian menyimpan fungsi hook di objek cbs
. Saat mengeksekusi, Anda dapat menggunakan fungsi patch()
:
fungsi ekspor init( modul: Array<Parsial<Modul>>, domApi?: DOMAPI, pilihan?: Pilihan ) { // ... mengembalikan tambalan fungsi ( oldVnode: VNode |.Elemen |. vnode: VNode ): VNode { // ... // patch dimulai, jalankan pre hook. for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); // ... } }
Di sini kita mengambil pre
hook sebagai contoh. Waktu eksekusi pre
hook adalah saat patch mulai dijalankan. Anda dapat melihat bahwa fungsi patch()
secara siklis memanggil hook pre
yang disimpan di cbs
pada awal eksekusi. Panggilan ke fungsi siklus hidup lainnya serupa dengan ini. Anda dapat melihat panggilan fungsi siklus hidup terkait di tempat lain di kode sumber.
Ide desain di sini adalah pola pengamat . Snabbdom mengimplementasikan fungsi non-inti dengan mendistribusikannya ke dalam modul. Dikombinasikan dengan definisi siklus hidup, modul dapat menentukan hook yang diinginkannya. Kemudian ketika init()
dijalankan, modul tersebut diproses menjadi objek cbs
untuk mendaftarkan hook ini; ketika waktu eksekusi tiba, panggil Kait ini digunakan untuk memberi tahu pemrosesan modul. Ini memisahkan kode inti dan kode modul. Dari sini kita dapat melihat bahwa pola pengamat adalah pola umum untuk pemisahan kode.
Selanjutnya kita sampai pada fungsi inti Kangkang patch()
. Fungsi ini dikembalikan setelah panggilan init()
. Fungsinya untuk memasang dan memperbarui VNode. Tanda tangannya adalah sebagai berikut:
function patch(oldVnode: VNode | Element |.DocumentFragment , vnode: VNode): VNode { // Demi kesederhanaan, jangan perhatikan DocumentFragment. // ... }
Parameter oldVnode
adalah elemen VNode atau DOM lama atau fragmen dokumen, dan parameter vnode
adalah objek yang diperbarui. Disini saya langsung memposting deskripsi prosesnya:
memanggil pre
hook yang terdaftar pada modul.
Jika oldVnode
adalah Element
, ia diubah menjadi objek vnode
kosong, dan elm
dicatat dalam atribut.
Penilaian di sini adalah apakah itu Element
(oldVnode as any).nodeType === 1
selesai. nodeType === 1
menunjukkan bahwa itu adalah ELEMENT_NODE, yang didefinisikan di sini.
Kemudian tentukan apakah oldVnode
dan vnode
sama. sameVnode()
akan dipanggil di sini untuk menentukan:
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { //Kunci yang sama. const isSameKey = vnode1.key === vnode2.key; // Komponen web, nama tag elemen khusus, lihat di sini: // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement const isSameIs = vnode1.data?.is === vnode2.data?.is; //Pemilih yang sama. const isSameSel = vnode1.sel === vnode2.sel; // Ketiganya sama. kembalikan isSameSel && isSameKey && isSameIs; }
patchVnode()
untuk pembaruan berbeda.createElm()
untuk membuat node DOM baru setelah pembuatan, masukkan node DOM dan hapus node DOM lama.Node baru dapat disisipkan dengan memanggil antrian kait insert
yang terdaftar di objek vnode yang terlibat dalam operasi di atas, patchVnode()
createElm()
. Adapun mengapa hal ini dilakukan, akan disebutkan di createElm()
.
Terakhir, post
hook yang terdaftar pada modul dipanggil.
Prosesnya pada dasarnya adalah melakukan diff jika vnodenya sama, dan jika berbeda, buat yang baru dan hapus yang lama. Selanjutnya, mari kita lihat bagaimana createElm()
membuat node DOM.
createElm()
membuat node DOM berdasarkan konfigurasi vnode. Prosesnya sebagai berikut:
panggil init
hook yang mungkin ada pada objek vnode.
Kemudian kita akan menghadapi beberapa situasi:
if vnode.sel === '!'
, ini adalah metode yang digunakan oleh Snabbdom untuk menghapus node asli, sehingga node komentar baru akan dimasukkan. Karena node lama akan dihapus setelah createElm()
, pengaturan ini dapat mencapai tujuan penghapusan instalasi.
Jika definisi pemilih vnode.sel
ada:
parsing pemilih dan dapatkan id
, tag
dan class
.
Panggil document.createElement()
atau document.createElementNS
untuk membuat node DOM, catat di vnode.elm
, dan atur id
, tag
, dan class
berdasarkan hasil langkah sebelumnya.
Panggil kait create
pada modul.
Proses array children
:
Jika children
adalah array, panggil createElm()
secara rekursif untuk membuat node anak, lalu panggil appendChild
untuk memasangnya di bawah vnode.elm
.
Jika children
bukan array tetapi vnode.text
ada, berarti konten elemen ini adalah teks. Saat ini, createTextNode
dipanggil untuk membuat node teks dan dipasang di bawah vnode.elm
.
Panggil kait create
di vnode. Dan tambahkan insert
hook di vnode ke antrian insert
hook.
Situasi yang tersisa adalah vnode.sel
tidak ada, menunjukkan bahwa node itu sendiri adalah teks, lalu panggil createTextNode
untuk membuat node teks dan rekam ke vnode.elm
.
Akhirnya kembalikan vnode.elm
.
Dapat dilihat dari keseluruhan proses bahwa createElm()
memilih cara membuat node DOM berdasarkan pengaturan pemilih sel
yang berbeda. Ada detail yang perlu ditambahkan di sini: antrian kait insert
yang disebutkan di patch()
. Alasan mengapa antrian kait insert
ini diperlukan adalah karena perlu menunggu hingga DOM benar-benar dimasukkan sebelum menjalankannya, dan juga perlu menunggu hingga semua node turunan dimasukkan, sehingga kita dapat menghitung informasi ukuran dan posisi dari antrian tersebut. elemen dalam insert
agar akurat. Dikombinasikan dengan proses pembuatan node anak di atas, createElm()
merupakan panggilan rekursif untuk membuat node anak, sehingga antrian akan mencatat node anak terlebih dahulu, lalu dirinya sendiri. Dengan cara ini pesanan dapat dijamin ketika menjalankan antrian di akhir patch()
.
Selanjutnya mari kita lihat bagaimana Snabbdom menggunakan patchVnode()
untuk melakukan diff, yang merupakan inti dari DOM virtual. Alur pemrosesan patchVnode()
adalah sebagai berikut:
pertama-tama jalankan hook prepatch
di vnode.
Jika oldVnode dan vnode merupakan referensi objek yang sama, keduanya akan dikembalikan secara langsung tanpa pemrosesan.
Panggil kait update
pada modul dan vnode.
Jika vnode.text
tidak ditentukan, beberapa kasus children
akan ditangani:
jika oldVnode.children
dan vnode.children
keduanya ada dan tidak sama. Kemudian panggil updateChildren
untuk memperbarui.
vnode.children
ada tetapi oldVnode.children
tidak ada. Jika oldVnode.text
ada, hapus terlebih dahulu, lalu panggil addVnodes
untuk menambahkan vnode.children
baru.
vnode.children
tidak ada tetapi oldVnode.children
ada. Panggil removeVnodes
untuk menghapus oldVnode.children
.
Jika oldVnode.children
atau vnode.children
tidak ada. Hapus oldVnode.text
jika ada.
Jika vnode.text
didefinisikan dan berbeda dari oldVnode.text
. Jika oldVnode.children
ada, panggil removeVnodes
untuk menghapusnya. Kemudian atur konten teks melalui textContent
.
Terakhir jalankan hook postpatch
di vnode.
Dapat dilihat dari proses bahwa perubahan pada atribut terkait dari nodenya sendiri di diff, seperti class
, style
, dll., diperbarui oleh modul. Namun, kami tidak akan memperluas terlalu banyak di sini Jika perlu, Anda dapat melihat kode terkait modul. Pemrosesan inti utama diff difokuskan pada children
. Selanjutnya, Kangkang diff memproses beberapa fungsi terkait children
.
sangat sederhana. Pertama panggil createElm()
untuk membuatnya, lalu masukkan ke induk yang sesuai.
destory
remove
destory
, kait ini dipanggil terlebih dahulu. Logikanya adalah memanggil hook pada objek vnode terlebih dahulu, lalu memanggil hook pada modul. Kemudian hook ini dipanggil secara rekursif di vnode.children
dalam urutan ini.remove
, kait ini hanya akan terpicu ketika elemen saat ini dihapus dari induknya. Elemen anak dalam elemen yang dihapus tidak akan terpicu, dan kait ini akan dipanggil pada modul dan objek vnode modul terlebih dahulu. lalu panggil vnode. Yang lebih istimewanya adalah elemen tersebut tidak akan benar-benar dihapus sampai semua remove
dipanggil. Hal ini dapat mencapai beberapa persyaratan penghapusan tertunda.Terlihat dari penjelasan di atas bahwa logika pemanggilan kedua hook ini berbeda. Khususnya, remove
hanya akan dipanggil pada elemen yang terpisah langsung dari induknya.
updateChildren()
digunakan untuk memproses perbedaan node anak, dan ini juga merupakan fungsi yang relatif kompleks di Snabbdom. Ide umumnya adalah menetapkan total empat penunjuk kepala dan ekor untuk oldCh
dan newCh
. Keempat penunjuk ini masing-masing adalah oldStartIdx
, oldEndIdx
, newStartIdx
dan newEndIdx
. Kemudian bandingkan dua array dalam loop while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
untuk menemukan bagian yang sama untuk digunakan kembali dan diperbarui, dan naikkan ke sepasang pointer untuk setiap perbandingan. Proses traversal detailnya diproses dengan urutan sebagai berikut:
Jika salah satu dari empat pointer menunjuk ke vnode == null, maka pointer berpindah ke tengah, seperti: start++ atau end--, kemunculan null akan dijelaskan nanti.
Jika node awal lama dan baru sama, yaitu sameVnode(oldStartVnode, newStartVnode)
mengembalikan nilai true, gunakan patchVnode()
untuk melakukan diff, dan kedua node awal akan bergerak satu langkah ke tengah.
Jika node akhir lama dan baru sama, patchVnode()
juga digunakan, dan kedua node akhir mundur satu langkah ke tengah.
Jika node awal yang lama sama dengan node akhir yang baru, gunakan patchVnode()
untuk memproses pembaruan terlebih dahulu. Kemudian node DOM yang sesuai dengan oldStart perlu dipindahkan. Strategi perpindahannya adalah pindah sebelum node saudara berikutnya dari node DOM yang sesuai dengan oldEndVnode
. Mengapa gerakannya seperti ini? Pertama-tama, oldStart sama dengan newEnd, yang berarti bahwa dalam pemrosesan loop saat ini, node awal dari array lama dipindahkan ke kanan; karena setiap pemrosesan memindahkan penunjuk kepala dan ekor ke tengah, kami memperbarui array lama ke yang baru. Saat ini oldEnd mungkin belum diproses, tetapi saat ini oldStart telah ditentukan sebagai yang terakhir dalam pemrosesan array baru saat ini, jadi masuk akal untuk pindah ke saudara berikutnya. simpul dari oldEnd. Setelah perpindahan selesai, oldStart++ dan newEnd-- berpindah satu langkah ke tengah array masing-masing.
Jika simpul akhir yang lama sama dengan simpul awal yang baru, patchVnode()
digunakan untuk memproses pembaruan terlebih dahulu, lalu simpul DOM yang terkait dengan oldEnd dipindahkan ke simpul DOM yang terkait dengan oldStartVnode
sama seperti langkah sebelumnya. Setelah pemindahan selesai, oldEnd--, newStart++.
Jika tidak ada satu pun hal di atas yang terjadi, gunakan kunci newStartVnode untuk menemukan idx subskrip di oldChildren
. Ada dua logika pemrosesan yang berbeda bergantung pada apakah subskrip ada:
Jika subskrip tidak ada, berarti newStartVnode baru dibuat. Buat DOM baru melalui createElm()
dan masukkan sebelum DOM yang sesuai dengan oldStartVnode
.
Jika subskrip ada, maka akan ditangani dalam dua kasus:
jika sel kedua vnode berbeda, maka akan tetap dianggap baru dibuat, buat DOM baru melalui createElm()
, dan masukkan sebelum DOM yang terkait dengan oldStartVnode
.
Jika sel sama, pembaruan diproses melalui patchVnode()
, dan vnode yang sesuai dengan subskrip oldChildren
disetel ke tidak terdefinisi. Inilah sebabnya == null muncul di traversal penunjuk ganda sebelumnya. Kemudian masukkan node yang diperbarui ke DOM yang sesuai dengan oldStartVnode
.
Setelah operasi di atas selesai, newStart++.
Setelah traversal selesai, masih ada dua situasi yang harus dihadapi. Salah satunya adalah oldCh
telah diproses sepenuhnya, tetapi masih ada node baru di newCh
, dan DOM baru perlu dibuat untuk setiap newCh
yang tersisa; yang lainnya adalah newCh
telah diproses sepenuhnya, dan masih ada node lama di oldCh
. Node yang berlebihan perlu dihilangkan. Kedua situasi tersebut ditangani sebagai berikut:
function updateChildren( parentElm: Simpul, oldCh: VNode[], Ch baru: VNode[], dimasukkanVnodeQueue: VNodeQueue ) { // Proses traversal penunjuk ganda. // ... // Ada node baru di newCh yang perlu dibuat. jika (newStartIdx <= newEndIdx) { //Perlu dimasukkan sebelum newEndIdx yang terakhir diproses. sebelum = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; tambahkanVnode( orang tuaElm, sebelum, Ch baru, Idx Mulai baru, IdxAkhir baru, dimasukkanVnodeQueue ); } // Masih ada node lama di oldCh yang perlu dihapus. jika (oldStartIdx <= oldEndIdx) { hapusVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
Mari kita gunakan contoh praktis untuk melihat proses pemrosesan updateChildren()
:
keadaan awal adalah sebagai berikut, array node anak lama adalah [A, B, C], dan array node baru adalah [B, A, C , D]:
Pada perbandingan putaran pertama, node awal dan akhir berbeda, jadi kita periksa apakah newStartVnode ada di node lama dan temukan posisi oldCh[1]. Kemudian jalankan patchVnode()
untuk memperbarui terlebih dahulu, lalu setel oldCh[1 ] = undefinisi , dan masukkan DOM sebelum oldStartVnode
, newStartIdx
mundur satu langkah, dan status setelah pemrosesan adalah sebagai berikut:
Pada perbandingan putaran kedua, oldStartVnode
dan newStartVnode
adalah sama. Ketika patchVnode()
dijalankan untuk memperbarui, oldStartIdx
dan newStartIdx
berpindah ke tengah.
Pada perbandingan putaran ketiga, oldStartVnode == null
, oldStartIdx
berpindah ke tengah, dan status diperbarui sebagai berikut:
Pada perbandingan putaran keempat, oldStartVnode
dan newStartVnode
adalah sama. Ketika patchVnode()
dijalankan untuk memperbarui, oldStartIdx
dan newStartIdx
berpindah ke tengah.
Saat ini, oldStartIdx
lebih besar dari oldEndIdx
, dan perulangan berakhir. Saat ini, masih ada node baru yang belum diproses di newCh
, dan Anda perlu memanggil addVnodes()
untuk memasukkannya. Status akhirnya adalah sebagai berikut:
, konten inti DOM virtual telah disortir di sini. Menurut saya prinsip desain dan implementasi Snabbdom sangat bagus ide-ide layak untuk dipelajari.