Halo, Habr! Saya membawa perhatian Anda pada terjemahan dari artikel "React Fiber Architecture" oleh Andrew Clark .
Entri
React Fiber adalah implementasi progresif dari algoritma React kunci. Ini adalah puncak dari studi dua tahun oleh tim pengembangan Bereaksi.
Tujuan Fiber adalah untuk meningkatkan produktivitas ketika mengembangkan tugas-tugas seperti animasi, mengatur elemen pada halaman, dan elemen bergerak. Fitur utamanya adalah rendering tambahan: kemampuan untuk membagi pekerjaan rendering menjadi unit dan mendistribusikannya di antara beberapa frame.
Fitur utama lainnya termasuk kemampuan untuk menjeda, membatalkan, atau menggunakan kembali pembaruan yang masuk dari pohon DOM, kemampuan untuk memprioritaskan berbagai jenis pembaruan, dan juga koordinasi primitif.
Sebelum membaca artikel ini, kami sarankan Anda membiasakan diri dengan prinsip dasar Bereaksi:
Ulasan
Apa itu rekonsiliasi?
Rekonsiliasi adalah algoritma Bereaksi yang digunakan untuk membedakan satu elemen pohon dari yang lain untuk menentukan bagian-bagian yang perlu diganti.
Pembaruan adalah perubahan data yang digunakan untuk membuat aplikasi Bereaksi. Ini biasanya merupakan hasil dari memanggil metode setState; Hasil akhir dari rendering komponen.
Gagasan utama React API adalah memikirkan pembaruan seolah-olah dapat menyebabkan perenderan lengkap aplikasi. Hal ini memungkinkan pengembang untuk bertindak secara deklaratif, dan tidak khawatir tentang seberapa rasional transisi aplikasi dari satu negara ke negara lain (dari A ke B, B ke C, C ke A, dll.).
Secara umum, merender seluruh aplikasi untuk setiap perubahan hanya berfungsi di aplikasi yang paling tradisional. Di dunia nyata, ini mempengaruhi kinerja. Undang-undang tersebut mencakup pengoptimalan yang membuat tampilan render lengkap tanpa memengaruhi sebagian besar kinerja. Sebagian besar optimasi ini melibatkan proses yang disebut rekonsiliasi.
Rekonsiliasi adalah algoritma di balik apa yang biasa kita sebut “Virtual DOM”. Definisi terdengar seperti ini: ketika Anda membuat aplikasi Bereaksi, elemen pohon yang menggambarkan aplikasi dihasilkan dalam memori yang dipesan. Pohon ini kemudian dimasukkan dalam lingkungan render - menggunakan contoh aplikasi browser, diterjemahkan ke dalam serangkaian operasi DOM. Ketika status aplikasi diperbarui (biasanya dengan memanggil setState), pohon baru dibuat. Pohon baru dibandingkan dengan yang sebelumnya untuk menghitung dan mengaktifkan dengan tepat operasi yang diperlukan untuk menggambar ulang aplikasi yang diperbarui.
Terlepas dari kenyataan bahwa Fiber adalah implementasi dekat dari rekonsiliator, algoritma tingkat tinggi yang dijelaskan dalam dokumentasi Bereaksi dalam kebanyakan kasus akan sama.
Konsep kunci:
- Berbagai jenis komponen menyarankan generasi pohon yang jauh berbeda. Bereaksi tidak akan mencoba untuk membandingkan mereka, tetapi cukup ganti pohon tua sepenuhnya.
- Daftar dibedakan menggunakan kunci. Kunci harus “gigih, dapat diprediksi, dan unik.”
Rekonsiliasi vs. Rendering
Pohon DOM adalah salah satu lingkungan yang dapat ditarik React, sisanya dapat dikaitkan dengan iOS asli dan Tampilan Android menggunakan React Native (Itulah sebabnya Virtual Dom adalah nama yang sedikit tidak pantas).
Alasan mengapa React mendukung begitu banyak tujuan adalah karena React dibangun sehingga rekonsiliasi dan rendering adalah fase yang terpisah. Rekonsiliator, yang bekerja, menghitung bagian mana dari pohon yang telah berubah, renderer kemudian menggunakan informasi ini untuk memperbarui pohon yang sebelumnya diberikan.
Pemisahan ini berarti React DOM dan React Native dapat menggunakan mekanisme rendernya sendiri saat menggunakan alat caching yang sama, yang terletak di React Core.
Fiber adalah implementasi yang dirancang ulang dari algoritma rekonsiliasi. Ini memiliki hubungan tidak langsung dengan rendering, sementara mekanisme rendering (render) dapat diubah untuk mendukung semua keuntungan dari arsitektur baru.
Perencanaan adalah proses yang menentukan kapan pekerjaan harus diselesaikan.
Kerja - setiap perhitungan yang harus dilakukan. Pekerjaan biasanya merupakan hasil dari pembaruan (misalnya, memanggil setState).
Prinsip-prinsip arsitektur React sangat baik sehingga mereka hanya dapat dijelaskan dengan kutipan ini:
Dalam implementasi Bereaksi saat ini, itu melintasi pohon secara rekursif dan memanggil fungsi rendering pada seluruh pohon yang diperbarui selama satu centang (16 ms). Namun, di masa depan, ia akan dapat membatalkan beberapa pembaruan untuk mencegah lompatan bingkai.
Ini adalah topik yang sering dibahas tentang Desain Bereaksi. Beberapa perpustakaan populer menerapkan pendekatan "push", di mana perhitungan dilakukan ketika data baru tersedia. Namun, Bereaksi mematuhi pendekatan tarikan, di mana perhitungan dapat dibatalkan bila perlu.
Bereaksi bukan perpustakaan untuk memproses data umum. Ini adalah perpustakaan untuk membangun antarmuka pengguna. Kami pikir itu harus memiliki posisi unik dalam aplikasi untuk menentukan perhitungan mana yang cocok dan mana yang tidak saat ini.
Jika ada sesuatu di balik layar, maka kita dapat membatalkan semua logika yang terkait dengannya. Jika data tiba lebih cepat dari kecepatan rendering bingkai, kami dapat menggabungkan pembaruan. Kami dapat meningkatkan prioritas pekerjaan yang datang sebagai hasil dari interaksi pengguna (seperti penampilan animasi ketika tombol ditekan) terhadap pekerjaan yang kurang penting di latar belakang (menampilkan konten baru yang dimuat dari server) untuk mencegah unduhan bingkai.
Konsep kunci:
- Dalam antarmuka pengguna, tidak penting setiap pembaruan diterapkan segera; pada kenyataannya, perilaku ini akan berlebihan, itu akan berkontribusi pada jatuhnya bingkai dan kerusakan UX.
- Berbagai jenis pembaruan memiliki prioritas yang berbeda - pembaruan animasi harus berakhir lebih cepat daripada, katakanlah, memperbarui penyimpanan data.
- Pendekatan berbasis push mengharuskan aplikasi (Anda, pengembang) untuk memutuskan bagaimana merencanakan pekerjaan. Pendekatan berbasis tarik memungkinkan kerangka kerja untuk membuat keputusan untuk Anda.
Bereaksi pada saat ini tidak memiliki keuntungan perencanaan sampai batas yang signifikan; perbarui hasil untuk seluruh subtree akan ditarik segera. Memilih elemen-elemen dalam algoritma kernel React dengan hati-hati untuk menerapkan penjadwalan adalah ide utama Fiber.
Apa itu Fiber?
Kita akan membahas inti dari arsitektur React Fiber. Serat adalah abstraksi tingkat rendah di atas aplikasi daripada yang digunakan pengembang untuk berpikir. Jika Anda menganggap upaya Anda untuk memahaminya tanpa harapan, jangan merasa kecil hati (Anda tidak sendirian). Teruslah mencari dan akhirnya akan membuahkan hasil.
Dan begitu!
Kami telah mencapai tujuan utama arsitektur Fiber - membiarkan React mengambil keuntungan dari perencanaan. Secara khusus, kita harus dapat:
- berhenti bekerja dan kembali lagi nanti.
- memprioritaskan berbagai jenis pekerjaan.
- menggunakan kembali pekerjaan yang dilakukan sebelumnya.
- batalkan pekerjaan jika tidak diperlukan lagi.
Untuk melakukan semua ini, pertama-tama kita perlu membagi pekerjaan menjadi beberapa unit. Dalam arti tertentu, ini adalah serat. Serat mewakili satu unit kerja.
Untuk melangkah lebih jauh, mari kembali ke konsep dasar Bereaksi "komponen sebagai data fungsi" , yang sering dinyatakan sebagai:
v = f(d)
Dengan ini maka rendering aplikasi Bereaksi seperti memanggil fungsi yang tubuhnya berisi panggilan ke fungsi lain, dan sebagainya. Analogi ini berguna ketika memikirkan serat.
Cara bahwa komputer pada dasarnya memeriksa urutan pelaksanaan suatu program disebut tumpukan panggilan. Ketika fungsi selesai, wadah tumpukan baru ditambahkan ke tumpukan. Wadah tumpukan ini mewakili pekerjaan yang dilakukan oleh suatu fungsi.
Ketika bekerja dengan antarmuka pengguna, terlalu banyak pekerjaan yang dilakukan segera dan ini merupakan masalah, itu dapat menyebabkan lompatan dalam animasi dan akan terlihat sebentar-sebentar. Selain itu, beberapa pekerjaan ini mungkin tidak diperlukan jika diganti oleh pembaruan terbaru. Pada titik ini, perbandingan antara antarmuka pengguna dan fungsi divergen, karena komponen memiliki tanggung jawab yang lebih spesifik daripada fungsi pada umumnya.
Browser terbaru dan React Native mengimplementasikan API yang membantu menyelesaikan masalah ini:
requestIdleCallback mendistribusikan tugas sehingga fungsi dengan prioritas rendah dipanggil dalam periode sederhana, dan requestAnimationFrame mendistribusikan tugas sehingga fungsi yang sangat diprioritaskan dipanggil dalam bingkai berikutnya. Masalahnya adalah bahwa untuk menggunakan API ini Anda harus membagi pekerjaan rendering menjadi unit tambahan. Jika Anda hanya mengandalkan tumpukan panggilan, pekerjaan akan berlanjut sampai tumpukan kosong.
Bukankah lebih baik jika kita dapat menyesuaikan perilaku tumpukan panggilan untuk mengoptimalkan tampilan bagian dari antarmuka pengguna? Apakah lebih baik jika kita dapat memecahkan tumpukan panggilan untuk memanipulasi kontainer secara manual?
Ini adalah panggilan React Fiber. Serat adalah implementasi tumpukan baru yang dirancang untuk komponen Bereaksi. Anda dapat menganggap serat tunggal sebagai wadah tumpukan virtual.
Keuntungan dari implementasi tumpukan ini adalah Anda dapat menyimpan tumpukan kontainer di memori dan mengeksekusi kemudian (dan di mana) yang Anda inginkan. Ini adalah definisi penting untuk mencapai tujuan perencanaan Anda.
Selain perencanaan, tindakan manual dengan stack mengungkapkan potensi konsep seperti konsistensi (konkurensi) dan penanganan kesalahan (batas kesalahan).
Pada bagian selanjutnya, kita melihat struktur serat.
Struktur serat
Secara khusus, "serat" adalah objek JavaScript yang berisi informasi tentang komponen, input dan outputnya.
Serat konsisten dengan wadah tumpukan, tetapi juga konsisten dengan esensi komponen.
Berikut adalah beberapa sifat penting dari "serat" (Daftar ini tidak lengkap):
Jenis dan Kunci
Jenis dan kunci melayani serat serta elemen Bereaksi. Bahkan, ketika serat dibuat, kedua bidang ini disalin secara langsung.
Jenis serat menggambarkan komponen yang bersesuaian. Untuk komposisi komponen, tipe adalah fungsi atau kelas komponen. Untuk komponen layanan (div, span), tipenya adalah string.
Secara konseptual, tipe adalah fungsi yang eksekusinya dilacak oleh stack stack.
Seiring dengan jenisnya, kuncinya digunakan ketika membandingkan pohon untuk menentukan apakah serat dapat digunakan kembali.
Anak dan saudara kandung
Bidang-bidang ini menunjuk ke serat lain, menggambarkan struktur rekursif serat.
Anak serat sesuai dengan nilai yang dikembalikan sebagai hasil memanggil metode render pada komponen. Dalam contoh di bawah ini:
function Parent() { return <Child /> }
Parent Fiber Child sesuai dengan Child.
Bidang relatif (atau tetangga) digunakan jika render mengembalikan beberapa anak (fitur baru di Fibre):
function Parent() { return [<Child1 />, <Child2 />] }
Serat anak adalah daftar yang terhubung secara tunggal di kepala yang merupakan anak pertama. Jadi dalam contoh ini, Orang tua anak adalah Anak1, dan kerabat Anak1 adalah Anak2.
Kembali ke analogi kami dengan fungsi, Anda dapat menganggap serat anak sebagai fungsi yang disebut di bagian akhir (fungsi yang disebut ekor).
Contoh Wikipedia:
function foo(data) { a(data); return b(data); }
Dalam contoh ini, fungsi yang disebut ekor adalah b.
Nilai pengembalian (pengembalian)
Serat pengembalian adalah serat yang harus dikembalikan ke program setelah memproses serat saat ini. Ini sama dengan mengembalikan alamat wadah tumpukan.
Ini juga dapat dianggap sebagai serat induk.
Jika serat memiliki banyak serat anak, kembalinya setiap serat anak mengembalikan serat induk. Dalam contoh di atas, serat pengembalian dari Child1 dan Child2 adalah Induk.
Properti saat ini dan di-cache (pendingProps dan memorizedProps)
Secara konseptual, properti adalah argumen fungsi. Properti serat saat ini adalah satu set properti ini di awal eksekusi, yang di-cache adalah satu set di akhir eksekusi.
Ketika properti menunggu input di-cache, ini berarti bahwa output serat sebelumnya dapat digunakan kembali tanpa perhitungan apa pun.
Prioritas pekerjaan saat ini (pendingWorkPriority)
Jumlah pekerjaan penentu prioritas ditampilkan oleh serat. Modul tingkat prioritas dalam React ReactPrioritylevel mencakup berbagai tingkat prioritas dan apa yang mereka wakili.
Dimulai dengan pengecualian tipe NoWork, yaitu 0, angka yang lebih tinggi menentukan prioritas terendah. Misalnya, Anda dapat menggunakan fungsi berikut untuk memeriksa apakah prioritas serat lebih besar dari tingkat yang ditentukan:
function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority }
Fungsi ini hanya untuk tujuan ilustrasi; itu bukan bagian dari database React Fiber.
Penjadwal menggunakan bidang prioritas untuk menemukan unit kerja berikutnya yang dapat dilakukan. Kami akan membahas algoritma ini di bagian selanjutnya.
Alternatif (atau pasangan)
Memperbarui serat (flush) - ini berarti menampilkan outputnya di layar.
Serat dalam pengembangan (work-in-progress) - serat yang belum dibangun; dengan kata lain, itu adalah tumpukan kontainer yang belum dikembalikan.
Kapan saja, esensi komponen tidak memiliki lebih dari dua keadaan untuk serat yang sesuai dengan: serat dalam keadaan saat ini, serat yang diperbarui atau serat dalam pengembangan.
Serat saat ini diikuti oleh serat yang sedang dikembangkan, dan kemudian, pada gilirannya, serat diperbarui.
Keadaan serat berikutnya dibuat dengan malas menggunakan fungsi cloneFiber. Hampir selalu ketika membuat objek baru, cloneFiber akan berusaha untuk menggunakan kembali alternatif (pasangan) serat jika itu ada, sambil meminimalkan biaya sumber daya.
Anda harus menganggap ladang uap (atau alternatif) sebagai detail implementasi, tetapi muncul begitu sering dalam dokumentasi sehingga tidak mungkin untuk tidak menyebutkannya.
Kesimpulan adalah elemen layanan (atau serangkaian elemen layanan); daun node Bereaksi aplikasi. Mereka khusus untuk setiap lingkungan tampilan (misalnya, di browser adalah 'div', 'span', dll.). Di JSX, mereka dilambangkan sebagai nama tag huruf kecil.
Intinya: Saya sarankan mencoba fitur arsitektur React v16.0 baru