
Tentang apa ini?
Mari kita lihat metamorfosis reduksi dalam aplikasi Redux / NGRX saya selama beberapa tahun terakhir. Dimulai dengan oak switch-case
, melanjutkan dengan pemilihan dari objek dengan kunci dan berakhir dengan kelas dengan dekorator, blackjack dan TypeScript. Kami akan mencoba meninjau tidak hanya sejarah jalan ini, tetapi juga menemukan beberapa hubungan sebab akibat.
Jika Anda dan saya mengajukan pertanyaan tentang pembuangan boilerplate di Redux / NGRX, maka artikel ini dapat menarik bagi Anda.
Jika Anda sudah menggunakan pendekatan untuk memilih reducer dari objek dengan kunci dan sudah muak dengan itu, Anda dapat langsung beralih ke "reduksi berbasis kelas."
Kotak saklar cokelat
Biasanya switch-case
vanilla, tetapi bagi saya sepertinya ini sangat mendiskriminasi semua jenis switch-case
.
Jadi, mari kita lihat masalah khas penciptaan asinkron entitas, misalnya, Jedi.
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false,
Saya akan sangat jujur dan mengakui bahwa saya tidak pernah menggunakan switch-case
dalam latihan saya. Saya ingin percaya bahwa saya bahkan memiliki daftar alasan untuk ini:
switch-case
terlalu mudah untuk istirahat: Anda bisa lupa memasukkan break
, Anda bisa lupa tentang default
.switch-case
terlalu bertulang.switch-case
hampir O (n). Ini tidak terlalu penting dalam dirinya sendiri, karena Redux tidak membanggakan kinerja yang menakjubkan dalam dirinya sendiri, tetapi fakta ini membuat penikmat kecantikan batin saya marah.
Cara logis untuk menyisir semua ini ditawarkan oleh dokumentasi Redux resmi - untuk memilih peredam dari objek dengan kunci.
Memilih peredam dari objek dengan kunci
Idenya sederhana - setiap perubahan negara dapat digambarkan oleh fungsi negara dan tindakan, dan setiap fungsi tersebut memiliki kunci tertentu ( type
bidang dalam tindakan) yang sesuai dengannya. Karena type
adalah string, tidak ada yang mencegah kita mencari tahu objek untuk semua fungsi tersebut, di mana kuncinya adalah type
dan nilainya adalah fungsi konversi keadaan murni (reducer). Dalam hal ini, kita dapat memilih peredam yang diperlukan dengan kunci (O (1)), ketika tindakan baru tiba di peredam root.
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJediMap = { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), } const reducerJedi = (state = reducerJediInitialState, action) => {
Yang paling enak adalah logika di dalam reducerJedi
tetap sama untuk peredam apa pun, dan kita bisa menggunakannya kembali. Bahkan ada perpustakaan nano redux-create-reducer untuk ini .
import { createReducer } from 'redux-create-reducer' const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJedi = createReducer(reducerJediInitialState, { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), })
Sepertinya tidak ada yang terjadi. Benar, sesendok madu bukan tanpa satu tong tar:
- Untuk reduksi kompleks, kita harus meninggalkan komentar, karena metode ini tidak menyediakan jalan keluar dari kotak untuk memberikan beberapa informasi meta-penjelasan.
- Objek dengan banyak reduksi dan kunci tidak dibaca dengan baik.
- Setiap peredam hanya memiliki satu kunci. Tetapi bagaimana jika Anda ingin menjalankan peredam yang sama untuk beberapa game aksi?
Saya hampir menangis bahagia ketika saya pindah ke reduksi berbasis kelas, dan di bawah ini saya akan menjelaskan alasannya.
Pereduksi Berbasis Kelas
Roti:
- Metode kelas adalah reduksi kami, dan metode memiliki nama. Hanya informasi yang sangat meta yang memberi tahu apa yang dilakukan peredam ini.
- Metode kelas dapat didekorasi, yang merupakan cara deklaratif sederhana untuk menghubungkan pereduksi dan tindakan terkait (yaitu, tindakan, bukan hanya satu tindakan!)
- Di bawah tenda, Anda dapat menggunakan semua objek yang sama untuk mendapatkan O (1).
Pada akhirnya, saya ingin mendapatkan sesuatu seperti itu.
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' class ReducerJedi {
Saya melihat tujuannya, saya tidak melihat rintangan.
Langkah 1. Dekorator @Action
.
Kita perlu bahwa dalam dekorator ini kita dapat menempel sejumlah tindakan, dan bahwa ZhKshny ini disimpan sebagai semacam informasi meta, yang nantinya dapat diakses. Untuk melakukan ini, kita bisa menggunakan polyfill reflect-metadata yang luar biasa , yang ditambal Reflect .
const METADATA_KEY_ACTION = 'reducer-class-action-metadata' export const Action = (...actionTypes) => (target, propertyKey, descriptor) => { Reflect.defineMetadata(METADATA_KEY_ACTION, actionTypes, target, propertyKey) }
Langkah 2. Ubah kelas menjadi, pada kenyataannya, peredam.
Gambarlah sebuah lingkaran, gambarlah sebentar, dan sekarang sihir kecil dan dapatkan burung hantu!
Seperti yang kita ketahui, setiap peredam adalah fungsi murni yang mengambil status dan tindakan saat ini dan mengembalikan status baru. Kelas, tentu saja, fungsi, tetapi bukan yang kita butuhkan, dan kelas ES6 tidak dapat dipanggil tanpa yang new
. Secara umum, kita perlu mengubahnya.
Jadi, kita membutuhkan fungsi yang akan mengambil kelas saat ini, melalui setiap metodenya, mengumpulkan informasi meta dengan jenis tindakan, mengumpulkan objek dengan reduksi dan membuat peredam akhir dari objek ini.
Mari kita mulai dengan mengumpulkan informasi meta.
const getReducerClassMethodsWthActionTypes = (instance) => {
Sekarang kita dapat mengonversi koleksi yang dihasilkan menjadi objek
const getReducerMap = (methodsWithActionTypes) => methodsWithActionTypes.reduce((reducerMap, { method, actionType }) => { reducerMap[actionType] = method return reducerMap }, {})
Jadi fungsi terakhir mungkin terlihat seperti ini:
import { createReducer } from 'redux-create-reducer' const createClassReducer = (ReducerClass) => { const reducerClass = new ReducerClass() const methodsWithActionTypes = getReducerClassMethodsWthActionTypes( reducerClass, ) const reducerMap = getReducerMap(methodsWithActionTypes) const initialState = reducerClass.initialState const reducer = createReducer(initialState, reducerMap) return reducer }
Selanjutnya kita bisa menerapkannya ke kelas ReducerJedi
kita.
const reducerJedi = createClassReducer(ReducerJedi)
Langkah 3. Kami melihat apa yang terjadi sebagai hasilnya.
Bagaimana cara hidup?
Sesuatu yang kami tinggalkan di belakang layar:
- Bagaimana jika jenis tindakan yang sama sesuai dengan beberapa reduksi?
- Akan bagus untuk menambahkan Immer di luar kotak.
- Bagaimana jika kita ingin menggunakan kelas untuk membuat tindakan kita? Atau fungsi (pembuat tindakan)? Saya ingin dekorator tidak hanya dapat menerima jenis tindakan, tetapi juga pembuat tindakan.
Perpustakaan kelas peredam kecil memiliki semua fungsi ini dengan contoh tambahan.
Perlu dicatat bahwa gagasan menggunakan kelas untuk reduksi bukanlah hal baru. @amcdnl pernah membuat pustaka besar aksi-ngrx , tapi sepertinya dia sekarang telah mencetaknya dan beralih ke NGXS . Selain itu, saya ingin pengetikan yang lebih ketat dan mengatur ulang pemberat dalam bentuk khusus untuk fungsi Angular. Berikut adalah daftar perbedaan utama antara aksi peredam dan tindakan ngrx.
Jika Anda menyukai gagasan kelas untuk reduksi, maka Anda mungkin juga ingin menggunakan kelas untuk tindakan Anda. Lihatlah kelas aksi-fluks .
Saya harap Anda tidak membuang waktu dengan sia-sia, dan artikel itu setidaknya sedikit bermanfaat bagi Anda. Tolong tendang dan kritik. Kami akan belajar kode lebih baik bersama.