Selami Lebih Dalam Keuntungan dan Fitur NgRx
Diterbitkan: 2022-03-11Jika seorang pemimpin tim menginstruksikan pengembang untuk menulis banyak kode boilerplate alih-alih menulis beberapa metode untuk memecahkan masalah tertentu, mereka memerlukan argumen yang meyakinkan. Insinyur perangkat lunak adalah pemecah masalah; mereka lebih suka mengotomatiskan berbagai hal dan menghindari boilerplate yang tidak perlu.
Meskipun NgRx hadir dengan beberapa kode boilerplate, ia juga menyediakan alat yang kuat untuk pengembangan. Artikel ini menunjukkan bahwa menghabiskan sedikit lebih banyak waktu untuk menulis kode akan menghasilkan manfaat yang sepadan dengan usaha.
Sebagian besar pengembang mulai menggunakan manajemen negara ketika Dan Abramov merilis perpustakaan Redux. Beberapa mulai menggunakan manajemen negara karena itu adalah tren, bukan karena mereka tidak memilikinya. Pengembang yang menggunakan proyek "Hello World" standar untuk manajemen negara dapat dengan cepat menemukan diri mereka menulis kode yang sama berulang-ulang, meningkatkan kompleksitas tanpa keuntungan.
Akhirnya, beberapa menjadi frustrasi dan meninggalkan manajemen negara sama sekali.
Masalah Awal Saya Dengan NgRx
Saya pikir kekhawatiran boilerplate ini adalah masalah besar dengan NgRx. Pada awalnya, kami tidak dapat melihat gambaran besar di baliknya. NgRx adalah perpustakaan, bukan paradigma pemrograman atau pola pikir. Namun, untuk sepenuhnya memahami fungsionalitas dan kegunaan perpustakaan ini, kita harus sedikit memperluas pengetahuan kita dan fokus pada pemrograman fungsional. Saat itulah Anda mungkin bisa menulis kode boilerplate dan merasa senang karenanya. (Saya sungguh-sungguh.) Saya pernah menjadi skeptis NgRx; sekarang saya pengagum NgRx.
Beberapa waktu yang lalu, saya mulai menggunakan manajemen negara. Saya mengalami pengalaman boilerplate yang dijelaskan di atas, jadi saya memutuskan untuk berhenti menggunakan perpustakaan. Karena saya menyukai JavaScript, saya mencoba untuk mencapai setidaknya pengetahuan dasar tentang semua kerangka kerja populer yang digunakan saat ini. Inilah yang saya pelajari saat menggunakan React.
React memiliki fitur yang disebut Hooks. Sama seperti Komponen di Angular, Hooks adalah fungsi sederhana yang menerima argumen dan mengembalikan nilai. Sebuah kait dapat memiliki keadaan di dalamnya, yang disebut efek samping. Jadi, misalnya, sebuah tombol sederhana di Angular dapat diterjemahkan ke dalam Bereaksi seperti ini:
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }Seperti yang Anda lihat, ini adalah transformasi langsung:
- SimpleButtonComponent => SimpleButton
- @Input() nama => props.name
- templat => nilai kembali
Fungsi React SimpleButton kami memiliki satu karakteristik penting dalam dunia pemrograman fungsional: Ini adalah fungsi murni . Jika Anda membaca ini, saya kira Anda pernah mendengar istilah itu setidaknya sekali. NgRx.io mengutip fungsi murni dua kali dalam Konsep Utama:
- Perubahan status ditangani oleh fungsi murni yang disebut
reducersyang mengambil status saat ini dan tindakan terbaru untuk menghitung status baru. - Selektor adalah fungsi murni yang digunakan untuk memilih, menurunkan, dan menyusun bagian-bagian keadaan.
Di React, pengembang didorong untuk menggunakan Hooks sebagai fungsi murni sebanyak mungkin. Angular juga mendorong pengembang untuk menerapkan pola yang sama menggunakan paradigma Komponen Smart-Dumb.
Saat itulah saya menyadari bahwa saya tidak memiliki beberapa keterampilan pemrograman fungsional yang penting. Tidak butuh waktu lama untuk memahami NgRx, karena setelah mempelajari konsep kunci dari pemrograman fungsional, saya mendapat pesan “Aha! moment”: Saya telah meningkatkan pemahaman saya tentang NgRx dan ingin menggunakannya lebih banyak untuk lebih memahami manfaat yang ditawarkannya.
Artikel ini membagikan pengalaman belajar saya dan pengetahuan yang saya peroleh tentang NgRx dan pemrograman fungsional. Saya tidak menjelaskan API untuk NgRx atau cara memanggil tindakan atau menggunakan pemilih. Sebagai gantinya, saya membagikan mengapa saya mulai menghargai bahwa NgRx adalah perpustakaan yang hebat: Ini bukan hanya tren yang relatif baru, tetapi juga memberikan sejumlah manfaat.
Mari kita mulai dengan pemrograman fungsional .
Pemrograman Fungsional
Pemrograman fungsional merupakan paradigma yang sangat berbeda dengan paradigma lainnya. Ini adalah topik yang sangat kompleks dengan banyak definisi dan pedoman. Namun, pemrograman fungsional mengandung beberapa konsep inti dan mengetahuinya merupakan prasyarat untuk menguasai NgRx (dan JavaScript secara umum).
Konsep inti ini adalah:
- Fungsi Murni
- Negara yang Tidak Dapat Diubah
- Efek samping
Saya ulangi: Ini hanya sebuah paradigma, tidak lebih. Tidak ada library functional.js yang kami unduh dan gunakan untuk menulis perangkat lunak fungsional. Itu hanya cara berpikir tentang menulis aplikasi. Mari kita mulai dengan konsep inti yang paling penting: fungsi murni .
Fungsi Murni
Suatu fungsi dianggap sebagai fungsi murni jika mengikuti dua aturan sederhana:
- Melewati argumen yang sama selalu mengembalikan nilai yang sama
- Kurangnya efek samping yang dapat diamati yang terlibat di dalam eksekusi fungsi (perubahan status eksternal, memanggil operasi I/O, dll.)
Jadi fungsi murni hanyalah fungsi transparan yang menerima beberapa argumen (atau tidak ada argumen sama sekali) dan mengembalikan nilai yang diharapkan. Anda yakin bahwa memanggil fungsi ini tidak akan mengakibatkan efek samping, seperti jaringan atau mengubah beberapa status pengguna global.
Mari kita lihat tiga contoh sederhana:
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- Fungsi pertama adalah murni karena akan selalu mengembalikan jawaban yang sama ketika melewati argumen yang sama.
- Fungsi kedua tidak murni karena nondeterministik dan mengembalikan jawaban yang berbeda setiap kali dipanggil.
- Fungsi ketiga tidak murni karena menggunakan efek samping (memanggil
console.log).
Sangat mudah untuk membedakan apakah fungsinya murni atau tidak. Mengapa fungsi murni lebih baik daripada fungsi tidak murni? Karena lebih mudah untuk dipikirkan. Bayangkan Anda membaca beberapa kode sumber dan melihat panggilan fungsi yang Anda tahu murni. Jika nama fungsinya benar, Anda tidak perlu menjelajahinya; Anda tahu bahwa itu tidak mengubah apa pun, itu mengembalikan apa yang Anda harapkan. Ini penting untuk debugging ketika Anda memiliki aplikasi perusahaan besar dengan banyak logika bisnis, karena dapat menjadi penghemat waktu yang sangat besar.
Juga, itu sederhana untuk menguji. Anda tidak perlu menyuntikkan apa pun atau mengejek beberapa fungsi di dalamnya, Anda cukup memberikan argumen dan menguji apakah hasilnya cocok. Ada hubungan yang kuat antara pengujian dan logika: Jika suatu komponen mudah untuk diuji, mudah untuk memahami bagaimana dan mengapa ia bekerja.
Fungsi murni hadir dengan fungsi yang sangat praktis dan ramah kinerja yang disebut memoisasi. Jika kita tahu bahwa memanggil argumen yang sama akan mengembalikan nilai yang sama maka kita cukup menyimpan hasil dan tidak membuang waktu untuk memanggilnya lagi. NgRx pasti duduk di atas memoisasi; itulah salah satu alasan utama mengapa itu cepat.
Anda mungkin bertanya pada diri sendiri, “Bagaimana dengan efek sampingnya? Kemana mereka pergi?" Dalam pembicaraan GOTO-nya, Russ Olsen bercanda bahwa klien kami tidak membayar kami untuk fungsi murni, mereka membayar kami untuk efek samping. Itu benar: Tidak ada yang peduli dengan fungsi murni Kalkulator jika tidak dicetak di suatu tempat. Efek samping memiliki tempatnya di dunia pemrograman fungsional. Kita akan melihatnya sebentar lagi.
Untuk saat ini, mari kita beralih ke langkah berikutnya dalam memelihara arsitektur aplikasi yang kompleks, konsep inti berikutnya: keadaan yang tidak dapat diubah .
Negara yang Tidak Dapat Diubah
Ada definisi sederhana untuk keadaan yang tidak dapat diubah:
- Anda hanya dapat membuat atau menghapus status. Anda tidak dapat memperbaruinya.
Secara sederhana, untuk memperbarui usia objek Pengguna ... :
let user = { username:"admin", age:28 }… Anda harus menulisnya seperti ini:
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }Setiap perubahan adalah objek baru yang telah menyalin properti dari yang lama. Dengan demikian, kita sudah dalam bentuk keadaan yang tidak dapat diubah.
String, Boolean, dan Number semuanya adalah status yang tidak dapat diubah: Anda tidak dapat menambahkan atau mengubah nilai yang ada. Sebaliknya, Date adalah objek yang bisa berubah: Anda selalu memanipulasi objek tanggal yang sama.
Kekekalan berlaku di seluruh aplikasi: Jika Anda melewatkan objek pengguna di dalam fungsi yang mengubah usianya, itu tidak boleh mengubah objek pengguna, itu harus membuat objek pengguna baru dengan usia yang diperbarui dan mengembalikannya:
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);Mengapa kita harus mencurahkan waktu dan perhatian untuk ini? Ada beberapa manfaat yang perlu digarisbawahi.
Salah satu manfaat untuk bahasa pemrograman back-end melibatkan pemrosesan paralel. Jika perubahan status tidak bergantung pada referensi dan setiap pembaruan adalah objek baru, Anda dapat membagi proses menjadi beberapa bagian dan mengerjakan tugas yang sama dengan utas yang tak terhitung jumlahnya tanpa berbagi memori yang sama. Anda bahkan dapat memparalelkan tugas di seluruh server.
Untuk kerangka kerja seperti Angular dan React, pemrosesan paralel adalah salah satu cara yang lebih bermanfaat untuk meningkatkan kinerja aplikasi. Misalnya, Angular harus memeriksa properti setiap objek yang Anda lewati melalui binding Input untuk membedakan apakah suatu komponen harus dirender atau tidak. Tetapi jika kita menetapkan ChangeDetectionStrategy.OnPush alih-alih default, itu akan memeriksa dengan referensi dan bukan oleh setiap properti. Dalam aplikasi besar, ini pasti menghemat waktu. Jika kami memperbarui status kami secara permanen, kami mendapatkan peningkatan kinerja ini secara gratis.
Manfaat lain untuk status yang tidak dapat diubah yang dimiliki oleh semua bahasa pemrograman dan kerangka kerja serupa dengan manfaat fungsi murni: Lebih mudah untuk dipikirkan dan diuji. Ketika perubahan adalah keadaan baru yang lahir dari yang lama, Anda tahu persis apa yang sedang Anda kerjakan dan Anda dapat melacak dengan tepat bagaimana dan di mana keadaan berubah. Anda tidak kehilangan riwayat pembaruan dan Anda dapat membatalkan/mengulangi perubahan status (React DevTools adalah salah satu contohnya).
Namun, jika satu negara bagian diperbarui, Anda tidak akan mengetahui riwayat perubahan tersebut. Pikirkan keadaan yang tidak dapat diubah seperti riwayat transaksi untuk rekening bank. Ini praktis harus dimiliki.
Sekarang setelah kita meninjau kekekalan dan kemurnian, mari kita bahas konsep inti yang tersisa: efek samping .
Efek samping
Kita dapat menggeneralisasi definisi efek samping:
- Dalam ilmu komputer, suatu operasi, fungsi, atau ekspresi dikatakan memiliki efek samping jika memodifikasi beberapa nilai variabel keadaan di luar lingkungan lokalnya. Artinya ia memiliki efek yang dapat diamati selain mengembalikan nilai (efek utama) ke pemanggil operasi.
Sederhananya, semua yang mengubah status di luar cakupan fungsi—semua operasi I/O dan beberapa pekerjaan yang tidak terhubung langsung ke fungsi—dapat dianggap sebagai efek samping. Namun, kita harus menghindari penggunaan efek samping di dalam fungsi murni karena efek samping bertentangan dengan filosofi pemrograman fungsional. Jika Anda menggunakan operasi I/O di dalam fungsi murni, maka itu berhenti menjadi fungsi murni.
Namun demikian, kita perlu memiliki efek samping di suatu tempat, karena aplikasi tanpanya tidak akan ada gunanya. Di Angular, tidak hanya fungsi murni yang perlu dilindungi dari efek samping, kita juga harus menghindari penggunaannya dalam Komponen dan Arahan.
Mari kita periksa bagaimana kita dapat menerapkan keindahan teknik ini di dalam kerangka Angular.
Pemrograman Sudut Fungsional
Salah satu hal pertama yang harus dipahami tentang Angular adalah kebutuhan untuk memisahkan komponen menjadi komponen yang lebih kecil sesering mungkin untuk memungkinkan perawatan dan pengujian yang lebih mudah. Ini perlu, karena kita perlu membagi logika bisnis kita. Juga, pengembang Angular didorong untuk meninggalkan komponen hanya untuk tujuan rendering dan memindahkan semua logika bisnis di dalam layanan.
Untuk memperluas konsep ini, pengguna Angular menambahkan pola “Dumb-Smart Component” ke kosakata mereka. Pola ini mengharuskan panggilan layanan tidak ada di dalam komponen kecil. Karena logika bisnis berada di layanan, kami masih harus memanggil metode layanan ini, menunggu responsnya, dan baru kemudian membuat perubahan status apa pun. Jadi, komponen memiliki beberapa logika perilaku di dalamnya.
Untuk menghindarinya, kita dapat membuat satu Smart Component (Root Component), yang berisi logika bisnis dan perilaku, meneruskan status melalui Properti Input, dan memanggil tindakan yang mendengarkan parameter Output. Dengan cara ini, komponen kecil benar-benar hanya untuk tujuan rendering. Tentu saja, Komponen Root kami harus memiliki beberapa panggilan layanan di dalamnya dan kami tidak dapat menghapusnya begitu saja tetapi utilitasnya akan terbatas pada logika bisnis saja, bukan rendering.
Mari kita lihat contoh Counter Component. Penghitung adalah komponen yang memiliki dua tombol yang menambah atau mengurangi nilai, dan satu displayField tampilan yang menampilkan nilai currentValue . Jadi kita berakhir dengan empat komponen:
- KonterContainer
- TingkatkanTombol
- Tombol Turun
- Nilai sekarang
Semua logika hidup di dalam CounterContainer , jadi ketiganya hanyalah penyaji. Berikut kode untuk ketiganya:
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }Lihat betapa sederhana dan murninya mereka. Mereka tidak memiliki status atau efek samping, mereka hanya bergantung pada properti input dan peristiwa pemancar. Bayangkan betapa mudahnya menguji mereka. Kita dapat menyebutnya sebagai komponen murni karena itulah mereka sebenarnya. Mereka hanya bergantung pada parameter input, tidak memiliki efek samping, dan selalu mengembalikan nilai yang sama (string template) dengan melewatkan parameter yang sama.

Jadi fungsi murni dalam pemrograman fungsional ditransfer ke komponen murni di Angular. Tapi kemana perginya semua logika? Logikanya masih ada tapi di tempat yang sedikit berbeda yaitu CounterComponent .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } Seperti yang Anda lihat, logika perilaku hidup di CounterContainer tetapi bagian rendering tidak ada (itu mendeklarasikan komponen di dalam template) karena bagian rendering adalah untuk komponen murni.
Kami dapat menyuntikkan layanan sebanyak yang kami inginkan karena kami menangani semua manipulasi data dan perubahan status di sini. Satu hal yang perlu disebutkan adalah bahwa jika kita memiliki komponen bersarang dalam, kita tidak boleh membuat hanya satu komponen tingkat akar. Kita dapat membaginya menjadi komponen pintar yang lebih kecil dan menggunakan pola yang sama. Pada akhirnya, itu tergantung pada kompleksitas dan tingkat bersarang untuk setiap komponen.
Kita dapat dengan mudah melompat dari pola itu ke perpustakaan NgRx itu sendiri, yang hanya satu lapisan di atasnya.
Perpustakaan NgRx
Kami dapat membagi aplikasi web apa pun menjadi tiga bagian inti:
- Logika bisnis
- Status Aplikasi
- Rendering Logika
Logika Bisnis adalah semua perilaku yang terjadi pada aplikasi, seperti jaringan, input, output, API, dll.
Status Aplikasi adalah status aplikasi. Itu bisa global, sebagai pengguna resmi saat ini, dan juga lokal, sebagai nilai Komponen Penghitung saat ini.
Rendering Logic mencakup rendering, seperti menampilkan data menggunakan DOM, membuat atau menghapus elemen, dan sebagainya.
Dengan menggunakan pola Dumb-Smart kami memisahkan Rendering Logic dari Business Logic dan Application State tetapi kami juga dapat membaginya karena keduanya secara konseptual berbeda satu sama lain. Status Aplikasi seperti snapshot aplikasi Anda saat ini. Logika Bisnis seperti fungsi statis yang selalu ada di aplikasi Anda. Alasan terpenting untuk membaginya adalah bahwa Logika Bisnis sebagian besar merupakan efek samping yang ingin kita hindari dalam kode aplikasi sebanyak mungkin. Inilah saatnya perpustakaan NgRx, dengan paradigma fungsionalnya, bersinar.
Dengan NgRx Anda memisahkan semua bagian ini. Ada tiga bagian utama:
- Pereduksi
- tindakan
- pemilih
Dikombinasikan dengan pemrograman fungsional, ketiganya bergabung untuk memberi kami alat yang ampuh untuk menangani aplikasi dari berbagai ukuran. Mari kita periksa masing-masing.
Pereduksi
Peredam adalah fungsi murni, yang memiliki tanda tangan sederhana. Dibutuhkan keadaan lama sebagai parameter dan mengembalikan keadaan baru, baik yang berasal dari yang lama atau yang baru. Status itu sendiri adalah satu objek, yang hidup dengan siklus hidup aplikasi Anda. Ini seperti tag HTML, objek root tunggal.
Anda tidak dapat secara langsung mengubah objek status, Anda perlu memodifikasinya dengan reduksi. Itu memiliki sejumlah manfaat:
- Logika perubahan keadaan hidup di satu tempat, dan Anda tahu di mana dan bagaimana keadaan berubah.
- Fungsi peredam adalah fungsi murni, yang mudah diuji dan dikelola.
- Karena reduksi adalah fungsi murni, reduksi dapat dimemoisasi, sehingga memungkinkan untuk di-cache dan menghindari komputasi tambahan.
- Perubahan negara tidak dapat diubah. Anda tidak pernah memperbarui instance yang sama. Sebaliknya, Anda selalu mengembalikan yang baru. Ini memungkinkan pengalaman debug "perjalanan waktu".
Ini adalah contoh sepele dari peredam:
function usernameReducer(oldState, username) { return {...oldState, username} }Meskipun ini adalah peredam dummy yang sangat sederhana, ini adalah kerangka untuk semua peredam yang panjang dan kompleks. Mereka semua berbagi manfaat yang sama. Kami dapat memiliki ratusan reduksi dalam aplikasi kami dan kami dapat membuat sebanyak yang kami inginkan.
Untuk Komponen Penghitung kami, status dan reduksi kami dapat terlihat seperti ini:
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }Kami menghapus status dari komponen. Sekarang kita membutuhkan cara untuk memperbarui status kita dan memanggil peredam yang sesuai. Saat itulah tindakan ikut bermain.
tindakan
Tindakan adalah cara untuk memberi tahu NgRx untuk memanggil peredam dan memperbarui status. Tanpa itu, tidak ada artinya menggunakan NgRx. Tindakan adalah objek sederhana yang kami lampirkan ke peredam saat ini. Setelah memanggilnya, peredam yang sesuai akan dipanggil, jadi dalam contoh kita, kita dapat memiliki tindakan berikut:
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);Tindakan kami melekat pada reduksi. Sekarang kita dapat memodifikasi Komponen Kontainer kita lebih lanjut dan memanggil tindakan yang sesuai bila perlu:
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Catatan: Kami menghapus status tersebut dan kami akan segera menambahkannya kembali .
Sekarang CounterContainer kami tidak memiliki logika perubahan status. Itu hanya tahu apa yang harus dikirim. Sekarang kita membutuhkan beberapa cara untuk menampilkan data ini ke view. Itulah kegunaan dari penyeleksi.
pemilih
Selektor juga merupakan fungsi murni yang sangat sederhana, tetapi tidak seperti peredam, ia tidak memperbarui status. Sesuai dengan namanya, pemilih hanya memilihnya. Dalam contoh kami, kami dapat memiliki tiga penyeleksi sederhana:
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } Dengan menggunakan selektor ini, kita dapat memilih setiap irisan status di dalam komponen CounterContainer pintar kita.
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Pilihan ini tidak sinkron secara default (seperti Observables pada umumnya). Ini tidak penting, setidaknya dari sudut pandang pola. Hal yang sama akan berlaku untuk yang sinkron, karena kami hanya akan memilih sesuatu dari status kami.
Mari kita mundur dan melihat gambaran besar untuk melihat apa yang telah kita capai sejauh ini. Kami memiliki Aplikasi Penghitung, yang memiliki tiga bagian utama yang hampir terpisah satu sama lain. Tidak ada yang tahu bagaimana status aplikasi mengelola dirinya sendiri atau bagaimana lapisan rendering membuat status.
Bagian yang dipisahkan menggunakan jembatan (Tindakan, Selektor) untuk terhubung satu sama lain. Mereka dipisahkan sedemikian rupa sehingga kami dapat mengambil seluruh kode Aplikasi Negara dan memindahkannya ke proyek lain, seperti untuk versi seluler misalnya. Satu-satunya hal yang harus kita terapkan adalah rendering. Tapi bagaimana dengan pengujian?
Menurut pendapat saya, pengujian adalah bagian terbaik dari NgRx. Menguji proyek sampel ini mirip dengan bermain tic-tac-toe. Hanya ada fungsi murni dan komponen murni, jadi mengujinya sangat mudah. Sekarang bayangkan jika proyek ini menjadi lebih besar, dengan ratusan komponen. Jika kami mengikuti pola yang sama, kami hanya akan menambahkan lebih banyak potongan bersama-sama. Itu tidak akan menjadi gumpalan kode sumber yang berantakan dan tidak dapat dibaca.
Kami hampir selesai. Hanya ada satu hal penting yang tersisa untuk dibahas: efek samping. Saya menyebutkan efek samping berkali-kali sejauh ini tetapi saya berhenti menjelaskan di mana harus menyimpannya.
Itu karena efek sampingnya adalah lapisan gula pada kue dan dengan membangun pola ini, sangat mudah untuk menghapusnya dari kode aplikasi.
Efek samping
Katakanlah Aplikasi Penghitung kita memiliki penghitung waktu di dalamnya dan setiap tiga detik secara otomatis meningkatkan nilainya satu per satu. Ini adalah efek samping yang sederhana, yang harus tinggal di suatu tempat. Ini adalah efek samping yang sama, menurut definisi, sebagai permintaan Ajax.
Jika kita berpikir tentang efek samping, sebagian besar memiliki dua alasan utama:
- Melakukan apapun di luar lingkungan negara
- Memperbarui status aplikasi
Misalnya, menyimpan beberapa status di dalam LocalStorage adalah opsi pertama, sementara memperbarui status dari respons Ajax adalah yang kedua. Tetapi keduanya memiliki ciri khas yang sama: Setiap efek samping harus memiliki titik awal. Itu perlu dipanggil setidaknya sekali untuk memintanya memulai tindakan.
Seperti yang kami uraikan sebelumnya, NgRx memiliki alat yang bagus untuk memberi seseorang perintah. Itu adalah tindakan. Kami dapat memanggil efek samping apa pun dengan mengirimkan tindakan. Kode semu bisa terlihat seperti ini:
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Ini cukup sepele. Seperti yang saya sebutkan sebelumnya, efek samping memperbarui sesuatu atau tidak. Jika efek samping tidak memperbarui apa pun, tidak ada yang bisa dilakukan; kita tinggalkan saja. Tetapi jika kita ingin memperbarui keadaan, bagaimana kita melakukannya? Dengan cara yang sama komponen mencoba memperbarui status: memanggil tindakan lain. Jadi kami memanggil tindakan di dalam efek samping, yang memperbarui status:
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Kami sekarang memiliki aplikasi yang berfungsi penuh.
Meringkas Pengalaman NgRx Kami
Ada beberapa topik penting yang ingin saya sebutkan sebelum kita menyelesaikan perjalanan NgRx kita:
- Kode yang ditampilkan adalah kode semu sederhana yang saya temukan untuk artikel tersebut; itu hanya cocok untuk tujuan demonstrasi. NgRx adalah tempat di mana sumber nyata hidup.
- Tidak ada pedoman resmi yang membuktikan teori saya tentang menghubungkan pemrograman fungsional dengan perpustakaan NgRx. Itu hanya pendapat saya yang terbentuk setelah membaca puluhan artikel dan contoh kode sumber yang dibuat oleh orang-orang yang sangat terampil.
- Setelah menggunakan NgRx, Anda pasti akan menyadari bahwa ini jauh lebih kompleks daripada contoh sederhana ini. Tujuan saya bukan untuk membuatnya terlihat lebih sederhana daripada yang sebenarnya, tetapi untuk menunjukkan kepada Anda bahwa meskipun agak rumit dan bahkan dapat menghasilkan jalur yang lebih panjang ke tujuan, itu sepadan dengan usaha tambahan.
- Penggunaan terburuk untuk NgRx adalah menggunakannya di mana-mana, terlepas dari ukuran atau kompleksitas aplikasi. Ada beberapa kasus di mana Anda tidak boleh menggunakan NgRx; misalnya dalam bentuk. Hampir tidak mungkin untuk mengimplementasikan formulir di dalam NgRx. Formulir direkatkan ke DOM itu sendiri; mereka tidak bisa hidup terpisah. Jika Anda mencoba memisahkannya, Anda akan membenci tidak hanya NgRx tetapi juga teknologi web secara umum.
- Terkadang menggunakan kode boilerplate yang sama, bahkan untuk contoh kecil, dapat berubah menjadi mimpi buruk, bahkan jika itu dapat menguntungkan kita di masa depan. Jika itu masalahnya, integrasikan saja dengan perpustakaan luar biasa lainnya, yang merupakan bagian dari ekosistem NgRx (ComponentStore).
