Rumah >hujung hadapan web >tutorial js >WTF Adakah Kereaktifan !?
Model Kereaktifan Diterangkan
Sudah (sudah) 10 tahun saya mula membangunkan aplikasi dan tapak web, tetapi ekosistem JavaScript tidak pernah lebih menarik daripada hari ini!
Pada tahun 2022, komuniti terpikat dengan konsep "Isyarat" sehingga kebanyakan rangka kerja JavaScript menyepadukannya ke dalam enjin mereka sendiri. Saya sedang memikirkan tentang Preact, yang telah menawarkan pembolehubah reaktif yang diasingkan daripada kitaran hayat komponen sejak September 2022; atau lebih baru Angular, yang melaksanakan Isyarat secara eksperimen pada Mei 2023, kemudian secara rasmi bermula dari versi 18. Pustaka JavaScript lain juga telah memilih untuk memikirkan semula pendekatan mereka...
Antara 2023 dan sehingga kini, saya telah menggunakan Signals secara konsisten merentas pelbagai projek. Kesederhanaan pelaksanaan dan penggunaannya telah meyakinkan saya sepenuhnya, sehinggakan saya telah berkongsi manfaatnya dengan rangkaian profesional saya semasa bengkel teknikal, sesi latihan dan persidangan.
Tetapi baru-baru ini, saya mula bertanya pada diri sendiri sama ada konsep ini benar-benar "revolusioner" / adakah terdapat alternatif kepada Isyarat? Jadi, saya mendalami refleksi ini dan menemui pendekatan yang berbeza untuk sistem reaktif.
Siaran ini ialah gambaran keseluruhan model kereaktifan yang berbeza, bersama-sama dengan pemahaman saya tentang cara ia berfungsi.
NB: Pada ketika ini, anda mungkin sudah meneka, saya tidak akan membincangkan tentang "Arus Reaktif" Java; jika tidak, saya akan menamakan siaran ini "WTF Is Backpressure!?" ?
Apabila kita bercakap tentang model kereaktifan, kita (pertama sekali) bercakap tentang "pengaturcaraan reaktif", tetapi terutamanya tentang "reaktiviti".
pengaturcaraan reaktif ialah paradigma pembangunan yang membolehkan secara automatik menyebarkan perubahan sumber data kepada pengguna.
Jadi, kita boleh mentakrifkan reaktiviti sebagai keupayaan untuk mengemas kini kebergantungan dalam masa nyata, bergantung pada perubahan data.
NB: Ringkasnya, apabila pengguna mengisi dan/atau menyerahkan borang, kami mesti bertindak balas terhadap perubahan ini, memaparkan komponen pemuatan atau apa-apa lagi yang menyatakan sesuatu sedang berlaku. .. Contoh lain, apabila menerima data secara tak segerak, kita mesti bertindak balas dengan memaparkan semua atau sebahagian daripada data ini, melaksanakan tindakan baharu, dsb.
Dalam konteks ini, perpustakaan reaktif menyediakan pembolehubah yang mengemas kini dan menyebarkan secara automatik dengan cekap, menjadikannya lebih mudah untuk menulis kod yang ringkas dan dioptimumkan.
Untuk menjadi cekap, sistem ini mesti mengira semula/menilai semula pembolehubah ini jika, dan hanya jika, nilainya telah berubah! Dengan cara yang sama, untuk memastikan data yang disiarkan kekal konsisten dan terkini, sistem mesti mengelak daripada memaparkan sebarang keadaan perantaraan (terutamanya semasa pengiraan perubahan keadaan).
NB: Keadaan merujuk kepada data/nilai yang digunakan sepanjang hayat program/aplikasi.
Baiklah, tetapi kemudian… Apakah sebenarnya "model kereaktifan" ini?
Model kereaktifan pertama dipanggil "PUSH" (atau kereaktifan "bersemangat"). Sistem ini adalah berdasarkan prinsip berikut:
Seperti yang anda duga, model "PUSH" bergantung pada corak reka bentuk "Boleh Diperhatikan/Pemerhati".
Mari kita pertimbangkan keadaan awal berikut,
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
Menggunakan perpustakaan reaktif (seperti RxJS), keadaan awal ini akan kelihatan lebih seperti ini:
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
NB: Demi siaran ini, semua coretan kod hendaklah dianggap sebagai "pseudo-code."
Sekarang, mari kita anggap bahawa pengguna (contohnya komponen) mahu merekodkan nilai keadaan D apabila sumber data ini dikemas kini,
d.subscribe((value) => console.log(value));
Komponen kami akan melanggan aliran data; ia masih perlu mencetuskan perubahan,
a.next({ firstName: "Jane", lastName: "Doe" });
Dari situ, sistem "PUSH" mengesan perubahan dan menyiarkannya secara automatik kepada pengguna. Berdasarkan keadaan awal di atas, berikut ialah perihalan operasi yang mungkin berlaku:
Salah satu cabaran sistem ini terletak pada susunan pengiraan. Sesungguhnya, berdasarkan kes penggunaan kami, anda akan mendapati bahawa D mungkin dinilai dua kali: kali pertama dengan nilai C dalam keadaan sebelumnya; dan kali kedua dengan nilai C terkini! Dalam model kereaktifan jenis ini, cabaran ini dipanggil "Masalah Berlian" ♦️.
Sekarang, mari kita anggap negeri bergantung pada dua sumber data utama,
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
Apabila mengemas kini E, sistem akan mengira semula keseluruhan keadaan, yang membolehkannya mengekalkan satu sumber kebenaran dengan menimpa keadaan sebelumnya.
Sekali lagi, "Masalah Berlian" berlaku... Kali ini pada sumber data C yang berpotensi dinilai 2 kali, dan sentiasa pada D.
"Masalah Berlian" bukanlah satu cabaran baharu dalam model kereaktifan "bersemangat". Sesetengah algoritma pengiraan (terutamanya yang digunakan oleh MobX) boleh menandai "nod pokok kebergantungan reaktif" untuk meratakan pengiraan keadaan. Dengan pendekatan ini, sistem akan terlebih dahulu menilai sumber data "root" (A dan E dalam contoh kami), kemudian B dan C, dan akhirnya D. Menukar susunan pengiraan keadaan membantu menyelesaikan masalah seperti ini.
Model kereaktifan kedua dipanggil "TARIK". Tidak seperti model "PUSH", ia berdasarkan prinsip berikut:
Peraturan terakhir inilah yang paling penting untuk diingat: tidak seperti sistem sebelumnya, yang terakhir ini menangguhkan pengiraan keadaan untuk mengelakkan berbilang penilaian sumber data yang sama.
Mari kekalkan keadaan awal sebelumnya...
Dalam sistem jenis ini, sintaks keadaan awal akan berada dalam bentuk berikut:
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
NB: Peminat reaksi berkemungkinan akan mengenali sintaks ini ?
Mengisytiharkan pembolehubah reaktif memberikan "kelahiran" kepada tuple: pembolehubah tidak berubah pada satu bahagian; kemas kini fungsi pembolehubah ini pada yang lain. Pernyataan selebihnya (B, C dan D dalam kes kami) dianggap sebagai keadaan terbitan kerana ia "mendengar" kebergantungan masing-masing.
d.subscribe((value) => console.log(value));
Ciri yang menentukan sistem "malas" ialah ia tidak menyebarkan perubahan serta-merta, tetapi hanya apabila diminta secara eksplisit.
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
Dalam model "PULL", menggunakan effect() (daripada komponen) untuk log nilai pembolehubah reaktif (dinyatakan sebagai kebergantungan) mencetuskan pengiraan perubahan keadaan:
Pengoptimuman sistem ini boleh dilakukan apabila menanyakan kebergantungan. Sesungguhnya, dalam senario di atas, A disoal dua kali untuk menentukan sama ada ia telah dikemas kini. Walau bagaimanapun, pertanyaan pertama mungkin cukup untuk menentukan sama ada keadaan telah berubah. C tidak perlu melakukan tindakan ini... Sebaliknya, A hanya boleh menyiarkan nilainya.
Mari kita rumitkan keadaan dengan menambah pembolehubah reaktif kedua "root",
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Sekali lagi, sistem menangguhkan pengiraan keadaan sehingga ia diminta secara eksplisit. Menggunakan kesan yang sama seperti sebelumnya, mengemas kini pembolehubah reaktif baharu akan mencetuskan langkah berikut:
Memandangkan nilai A tidak berubah, pengiraan semula pembolehubah ini adalah tidak perlu (perkara yang sama berlaku untuk nilai B). Dalam kes sedemikian, penggunaan algoritma memoisasi meningkatkan prestasi semasa pengiraan keadaan.
Model kereaktifan terakhir ialah sistem "PUSH-PULL". Istilah "PUSH" mencerminkan penyebaran segera pemberitahuan perubahan, manakala "TARIK" merujuk kepada pengambilan nilai keadaan atas permintaan. Pendekatan ini berkait rapat dengan apa yang dipanggil kereaktifan "berbutir halus", yang mematuhi prinsip berikut:
Perhatikan bahawa kereaktifan jenis ini tidak eksklusif untuk model "PUSH-PULL". Kereaktifan berbutir halus merujuk kepada penjejakan tepat kebergantungan sistem. Jadi, terdapat model kereaktifan PUSH dan PULL yang juga berfungsi dengan cara ini (saya sedang memikirkan Jotai atau Recoil.
Masih berdasarkan keadaan awal sebelumnya... Pengisytiharan keadaan awal dalam sistem kereaktifan "berbutir halus" akan kelihatan seperti ini:
let a = { firstName: "John", lastName: "Doe" }; const b = a.firstName; const c = a.lastName; const d = `${b} ${c}`;
NB: Penggunaan kata kunci isyarat bukan sekadar anekdot di sini ?
Dari segi sintaks, ia sangat serupa dengan model "PUSH", tetapi terdapat satu perbezaan yang ketara dan penting: kebergantungan! Dalam sistem kereaktifan "berbutir halus" , tidak perlu mengisytiharkan kebergantungan yang diperlukan secara eksplisit untuk mengira keadaan terbitan, kerana keadaan ini secara tersirat menjejaki pembolehubah yang mereka gunakan. Dalam kes kami, B dan C akan menjejaki perubahan pada nilai A secara automatik dan D akan menjejaki perubahan kepada kedua-dua B dan C.
let a = observable.of({ firstName: "John", lastName: "Doe" }); const b = a.pipe(map((a) => a.firstName)); const c = a.pipe(map((a) => a.lastName)); const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Dalam sistem sedemikian, mengemas kini pembolehubah reaktif adalah lebih cekap berbanding model asas "PUSH" kerana perubahan itu disebarkan secara automatik kepada pembolehubah terbitan yang bergantung padanya (hanya sebagai pemberitahuan, bukan nilai itu sendiri).
d.subscribe((value) => console.log(value));
Kemudian, atas permintaan (mari kita ambil contoh logger), penggunaan D dalam sistem akan mengambil nilai keadaan akar yang berkaitan (dalam kes kami A), hitung nilai daripada keadaan terbitan (B dan C), dan akhirnya menilai D. Bukankah ia mod operasi intuitif?
Mari kita pertimbangkan keadaan berikut,
a.next({ firstName: "Jane", lastName: "Doe" });
Sekali lagi, aspek "berbutir halus" sistem PUSH-PULL membolehkan pengesanan automatik setiap negeri. Jadi, keadaan terbitan C kini menjejaki keadaan akar A dan E. Mengemas kini pembolehubah E akan mencetuskan tindakan berikut:
Inilah perkaitan terdahulu bagi kebergantungan reaktif antara satu sama lain yang menjadikan model ini begitu cekap!
Sememangnya, dalam sistem "PULL" klasik (seperti DOM Maya React, contohnya), apabila mengemas kini keadaan reaktif daripada komponen, rangka kerja akan dimaklumkan tentang perubahan (mencetuskan " membezakan" fasa). Kemudian, apabila diminta (dan ditangguhkan), rangka kerja akan mengira perubahan dengan merentasi pokok pergantungan reaktif; setiap kali pembolehubah dikemas kini! "Penemuan" keadaan tanggungan ini mempunyai kos yang ketara...
Dengan sistem kereaktifan "berbutir halus" (seperti Isyarat), kemas kini pembolehubah reaktif/primitif secara automatik memberitahu mana-mana keadaan terbitan yang dipautkan kepada mereka tentang perubahan itu. Oleh itu, tidak perlu (semula) menemui kebergantungan yang berkaitan; penyebaran negeri disasarkan!
Pada tahun 2024, kebanyakan rangka kerja web telah memilih untuk memikirkan semula cara ia berfungsi, terutamanya dari segi model kereaktifan mereka. Peralihan ini telah menjadikan mereka secara amnya lebih cekap dan berdaya saing. Yang lain memilih untuk menjadi (masih) hibrid (saya memikirkan tentang Vue di sini), yang menjadikan mereka lebih fleksibel dalam banyak situasi.
Akhir sekali, apa sahaja model yang dipilih, pada pendapat saya, sistem reaktif (baik) dibina berdasarkan beberapa peraturan utama:
Perkara terakhir ini, yang boleh ditafsirkan sebagai prinsip asas pengaturcaraan deklaratif, ialah bagaimana saya melihat sistem reaktif (baik) perlu bersifat deterministik! Inilah "determinisme" yang menjadikan model reaktif boleh dipercayai, boleh diramal dan mudah digunakan dalam projek teknikal pada skala, tanpa mengira kerumitan algoritma.
Atas ialah kandungan terperinci WTF Adakah Kereaktifan !?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!