Bagian 1. Reaktivitas dan aliran
Seri artikel ini berfokus pada reaktivitas dan penerapannya di JS menggunakan perpustakaan yang luar biasa seperti RxJS.
Seri artikel "Dasar-dasar pemrograman reaktif menggunakan RxJS":
Untuk siapa artikel ini : pada dasarnya, di sini saya akan menjelaskan dasar-dasarnya, jadi artikel ini terutama ditujukan untuk pemula dalam teknologi ini. Pada saat yang sama, saya berharap bahwa pengembang yang berpengalaman akan dapat mempelajari sesuatu yang baru untuk diri mereka sendiri. Pemahaman akan membutuhkan pengetahuan tentang js (es5 / es6).
Motivasi : Saya pertama kali bertemu RxJS ketika saya mulai bekerja dengan sudut. Saat itulah saya kesulitan memahami mekanisme reaktivitas. Fakta bahwa pada saat awal pekerjaan saya sebagian besar artikel dikhususkan untuk versi lama perpustakaan menambah kesulitan. Saya harus membaca banyak dokumentasi, berbagai manual untuk setidaknya memahami sesuatu. Dan hanya setelah beberapa waktu saya mulai menyadari bagaimana "semuanya diatur". Untuk membuat hidup lebih mudah bagi orang lain, saya memutuskan untuk meletakkan semuanya di rak.
Apa itu reaktivitas?
Sulit untuk menemukan jawaban untuk istilah yang tampaknya umum. Singkatnya: reaktivitas adalah kemampuan untuk merespons setiap perubahan. Tapi perubahan apa yang sedang kita bicarakan? Pertama-tama, tentang perubahan data. Pertimbangkan sebuah contoh:
let a = 2; let b = 3; let sum = a + b; console.log(sum);
Contoh ini menunjukkan paradigma pemrograman imperatif yang akrab. Berbeda dengan pendekatan imperatif, pendekatan reaktif dibangun di atas strategi propagasi perubahan. Strategi push menyiratkan bahwa jika terjadi perubahan data, perubahan yang sama ini akan "didorong", dan data yang bergantung padanya akan diperbarui secara otomatis. Berikut adalah contoh perilaku kami jika strategi push diterapkan:
let a = 2; let b = 3; let sum = a + b; console.log(sum);
Contoh ini menunjukkan pendekatan reaktif. Perlu dicatat bahwa contoh ini tidak ada hubungannya dengan kenyataan, saya berikan hanya untuk menunjukkan perbedaan dalam pendekatan. Kode reaktif dalam aplikasi dunia nyata akan terlihat sangat berbeda, dan sebelum beralih ke praktik, kita harus membicarakan komponen reaktivitas penting lainnya.
Aliran data
Jika Anda melihat istilah "pemrograman reaktif" di Wikipedia, situs tersebut akan memberi kami definisi berikut: "Pemrograman reaktif adalah paradigma pemrograman yang berfokus pada aliran data dan penyebaran perubahan." Dari definisi ini, kita dapat menyimpulkan bahwa reaktivitas didasarkan pada dua "paus" utama. Saya menyebutkan distribusi perubahan di atas, jadi kami tidak akan membahas hal ini lebih lanjut. Tetapi kita harus berbicara lebih banyak tentang aliran data. Mari kita lihat contoh berikut:
const input = document.querySelector('input');
Kami mendengarkan acara keyup dan meletakkan objek acara di array kami. Seiring waktu, array kami dapat berisi ribuan objek KeyboardEvent. Perlu dicatat bahwa susunan kami diurutkan berdasarkan waktu - indeks acara yang lebih baru lebih besar daripada indeks yang sebelumnya. Array seperti itu adalah model aliran data yang disederhanakan. Mengapa disederhanakan? Karena array hanya dapat menyimpan data. Kita juga dapat mengulangi array dan memproses elemen-elemennya. Tetapi array tidak dapat memberi tahu kami bahwa elemen baru telah ditambahkan padanya. Untuk mengetahui apakah data baru telah ditambahkan ke array, kita harus mengulanginya lagi.
Tetapi bagaimana jika array kami tahu cara memberi tahu kami bahwa data baru tiba di dalamnya? Array seperti itu bisa dengan pasti disebut stream. Jadi, kita sampai pada definisi aliran. Aliran adalah array data yang diurutkan berdasarkan waktu yang dapat menunjukkan bahwa data telah berubah.
Diamati
Sekarang kita tahu apa itu aliran, mari bekerja bersama mereka. Dalam RxJS, stream diwakili oleh kelas yang dapat diobservasi. Untuk membuat aliran Anda sendiri, cukup panggil konstruktor kelas ini dan berikan fungsi berlangganan sebagai argumen:
const observable = new Observable(observer => { observer.next(1); observer.next(2); observer.complete(); })
Dengan memanggil konstruktor dari kelas Observable, kami membuat utas baru. Sebagai argumen, kami meneruskan fungsi berlangganan ke konstruktor. Fungsi berlangganan adalah fungsi reguler yang menggunakan pengamat sebagai parameter. Pengamat sendiri adalah objek yang memiliki tiga metode:
- selanjutnya - melempar nilai baru ke dalam aliran
- error - melempar kesalahan ke dalam stream, setelah itu stream berakhir
- complete - mengakhiri utas
Jadi kami membuat utas yang memancarkan dua nilai dan berakhir.
Berlangganan
Jika kita menjalankan kode sebelumnya, tidak ada yang terjadi. Kami hanya akan membuat aliran baru dan menyimpan tautan ke dalamnya dalam variabel yang dapat diamati, tetapi aliran itu sendiri tidak akan pernah memancarkan nilai tunggal. Ini karena utas adalah objek "malas" dan tidak melakukan apa pun sendiri. Agar stream kami mulai memancarkan nilai dan kami dapat memproses nilai-nilai ini, kami perlu mulai "mendengarkan" ke stream. Anda dapat melakukan ini dengan memanggil metode berlangganan pada objek yang dapat diamati.
const observer = { next: value => console.log(value),
Kami mengidentifikasi pengamat kami dan menjelaskan tiga metode untuknya: berikutnya, kesalahan, lengkap. Metode hanya mencatat data yang dilewatkan sebagai parameter. Kemudian kami memanggil metode berlangganan dan meneruskan pengamat kami ke sana. Pada saat panggilan berlangganan, fungsi berlangganan dipanggil, fungsi yang kami berikan kepada konstruktor pada tahap menyatakan aliran kami. Selanjutnya, kode fungsi berlangganan akan dieksekusi, yang meneruskan dua nilai kepada pengamat kami, dan kemudian menghentikan streaming.
Tentunya, banyak yang memiliki pertanyaan, apa yang akan terjadi jika kita berlangganan lagi? Semuanya akan sama: aliran lagi akan melewati dua nilai kepada pengamat dan berakhir. Setiap kali metode berlangganan dipanggil, fungsi berlangganan akan dipanggil, dan semua kode akan dieksekusi lagi. Dari sini kita dapat menyimpulkan: tidak peduli berapa kali kita berlangganan sungai, pengamat kita akan menerima data yang sama.
Berhenti berlangganan
Sekarang mari kita coba menerapkan contoh yang lebih kompleks. Kami akan menulis penghitung waktu yang akan menghitung detik dari saat berlangganan, dan mengirimkannya ke pengamat.
const timer = new Observable(observer => { let counter = 0;
Kode ini cukup sederhana. Di dalam fungsi berlangganan, kami mendeklarasikan variabel penghitung. Kemudian, menggunakan closure, kita mengakses variabel dari fungsi panah di setInterval. Dan setiap detik kami meneruskan variabel ke pengamat, setelah itu kami menambahkannya. Selanjutnya, berlangganan aliran, tentukan hanya satu metode - selanjutnya. Jangan khawatir bahwa kami belum mengumumkan metode lain. Tidak ada metode pengamat yang diperlukan. Kita bahkan dapat melewatkan objek kosong, tetapi dalam hal ini utas akan sia-sia.
Setelah diluncurkan, kita akan melihat log yang didambakan yang akan muncul setiap detik. Jika mau, Anda dapat mencoba dan berlangganan aliran beberapa kali. Anda akan melihat bahwa masing-masing utas akan dieksekusi secara terpisah dari yang lainnya.
Jika Anda memikirkannya, utas kami akan dieksekusi sepanjang umur seluruh aplikasi, karena kami tidak memiliki logika untuk membatalkan setInterval, dan dalam fungsi berlangganan tidak ada panggilan ke metode lengkap. Tetapi bagaimana jika kita membutuhkan utas untuk mengakhiri?
Padahal, semuanya sangat sederhana. Jika Anda melihat dokumentasi, Anda dapat melihat bahwa metode berlangganan mengembalikan objek berlangganan. Objek ini memiliki metode berhenti berlangganan. Kami menyebutnya, dan pengamat kami akan berhenti menerima nilai dari aliran.
const subscription = timer.subscribe({next: console.log}); setTimeout(() => subscription.unsubscribe(), 5000);
Setelah memulai, kita akan melihat bahwa penghitung berhenti pada 4. Namun, meskipun kami telah berhenti berlangganan dari aliran, fungsi setInterval kami terus berfungsi. Dia menambah counter kami setiap detik dan memberikannya kepada pengamat boneka. Untuk mencegah hal ini terjadi, Anda harus menulis logika untuk membatalkan interval. Untuk melakukan ini, Anda harus mengembalikan fungsi baru dari fungsi berlangganan di mana logika pembatalan akan diterapkan.
const timer = new Observable(observer => { let counter = 0; const intervalId = setInterval(() => { observer.next(counter++); }, 1000); return () => { clearInterval(intervalId); } });
Sekarang kita bisa bernapas lega. Setelah memanggil metode berhenti berlangganan, fungsi berhenti berlangganan kami akan dipanggil, yang akan menghapus interval.
Kesimpulan
Artikel ini menunjukkan perbedaan antara pendekatan imperatif dan reaktif, serta contoh menciptakan aliran Anda sendiri. Pada bagian selanjutnya saya akan berbicara tentang apa metode lain untuk membuat utas yang ada dan bagaimana menerapkannya.