
Apa yang akan kita bahas di sini?
Kita akan meninjau evolusi reduksi di aplikasi Redux / NGRX saya yang terjadi selama dua tahun terakhir. Mulai dari vanilla switch-case
, pergi ke memilih peredam dari objek dengan kunci, akhirnya diselesaikan dengan reduksi berbasis kelas. Kami tidak hanya akan berbicara tentang bagaimana, tetapi juga tentang mengapa.
Jika Anda tertarik untuk bekerja di sekitar terlalu banyak boilerplate di Redux / NGRX Anda mungkin ingin memeriksa artikel ini .
Jika Anda sudah terbiasa memilih peredam dari teknik peta, pertimbangkan untuk langsung beralih ke peredam berbasis kelas .
Kasing sakelar vanila
Jadi mari kita lihat tugas sehari-hari untuk membuat entitas di server secara tidak sinkron. Kali ini saya sarankan kita menjelaskan bagaimana kita bisa membuat jedi baru.
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,
Biarkan saya jujur, saya tidak pernah menggunakan reduksi semacam ini dalam produksi. Alasan saya ada tiga:
switch-case
memperkenalkan beberapa titik ketegangan, pipa bocor, yang kita mungkin lupa untuk menambal waktu di beberapa titik. Kita selalu bisa lupa untuk break
jika tidak segera return
, kita selalu bisa lupa untuk menambahkan default
, yang harus kita tambahkan ke setiap peredam.switch-case
memiliki beberapa kode boilerplate sendiri yang tidak menambahkan konteks apa pun.switch-case
adalah O (n), semacam . Ini bukan argumen yang kuat dengan sendirinya karena Redux tidak terlalu performant, tapi itu membuat perfeksionis batin saya marah.
Langkah logis berikutnya yang disarankan oleh dokumentasi resmi Redux adalah mengambil peredam dari suatu objek dengan kunci.
Memilih peredam dari objek dengan kunci
Idenya sederhana. Setiap transformasi keadaan adalah fungsi dari keadaan dan tindakan dan memiliki jenis tindakan yang sesuai. Mempertimbangkan bahwa setiap jenis tindakan adalah string, kita dapat membuat objek, di mana setiap kunci adalah jenis tindakan dan setiap nilai adalah fungsi yang mengubah keadaan (reducer). Lalu kita bisa memilih peredam yang diperlukan dari objek itu dengan kunci, yaitu O (1), ketika kita menerima tindakan baru.
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 keren di sini adalah bahwa logika di dalam reducerJedi
tetap sama untuk peredam apa pun, yang berarti kita dapat menggunakannya kembali. Bahkan ada perpustakaan kecil, yang disebut redux-create-reducer , yang melakukan hal itu. Itu membuat kode terlihat seperti 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, }), })
Bagus dan cantik, ya? Meskipun cantik ini masih memiliki beberapa peringatan:
- Dalam hal reduksi kompleks kita harus meninggalkan banyak komentar yang menjelaskan apa yang dilakukan peredam ini dan mengapa.
- Peta peredam besar sulit dibaca.
- Setiap peredam hanya memiliki satu jenis tindakan yang sesuai. Bagaimana jika saya ingin menjalankan peredam yang sama untuk beberapa tindakan?
Peredam berbasis kelas menjadi gudang cahaya saya di kerajaan malam itu.
Reduksi berbasis kelas
Kali ini saya akan mulai dengan mengapa pendekatan ini:
- Metode kelas akan menjadi reduksi kami dan metode memiliki nama, yang merupakan informasi meta yang berguna, dan kami dapat meninggalkan komentar dalam 90% kasus.
- Metode-metode kelas dapat didekorasi yang merupakan cara deklaratif yang mudah dibaca untuk mencocokkan aksi dan reduksi.
- Kita masih bisa menggunakan peta tindakan di bawah tenda untuk memiliki kompleksitas O (1).
Jika itu terdengar seperti daftar alasan yang masuk akal untuk Anda, mari gali!
Pertama-tama, saya ingin mendefinisikan apa yang ingin kita dapatkan sebagai hasilnya.
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' class ReducerJedi {
Sekarang seperti yang kita lihat di mana kita ingin mendapatkan kita bisa melakukannya langkah demi langkah.
Langkah 1. Dekorator aksi .
Apa yang ingin kita lakukan di sini adalah menerima sejumlah jenis tindakan dan menyimpannya sebagai meta-informasi untuk metode kelas yang akan digunakan nanti. Untuk melakukan itu kita bisa menggunakan polyfill reflect-metadata , yang membawa fungsionalitas meta-data ke objek Reflect . Setelah itu dekorator ini hanya akan melampirkan argumennya (jenis tindakan) ke metode sebagai meta-data.
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. Membuat fungsi peredam dari kelas peredam
Seperti yang kita ketahui, setiap peredam adalah fungsi murni yang menerima status dan aksi serta mengembalikan status baru. Nah, kelas adalah fungsi juga, tetapi kelas ES6 tidak dapat dipanggil tanpa yang new
dan kita harus membuat peredam aktual dari kelas dengan beberapa metode. Jadi kita perlu mengubahnya.
Kita membutuhkan fungsi yang akan mengambil kelas kita, berjalan melalui setiap metode, mengumpulkan metadata dengan jenis tindakan, membangun peta peredam dan membuat peredam akhir dari peta peredam itu.
Inilah cara kami memeriksa setiap metode dalam suatu kelas.
const getReducerClassMethodsWthActionTypes = (instance) => {
Sekarang kami ingin memproses koleksi yang diterima menjadi peta peredam.
const getReducerMap = (methodsWithActionTypes) => methodsWithActionTypes.reduce((reducerMap, { method, actionType }) => { reducerMap[actionType] = method return reducerMap }, {})
Jadi fungsi akhirnya bisa 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 }
Dan kita bisa menerapkannya ke kelas ReducerJedi
kita seperti ini.
const reducerJedi = createClassReducer(ReducerJedi)
Langkah 3. Menggabungkan semuanya.
Langkah selanjutnya
Inilah yang kami lewatkan:
- Bagaimana jika tindakan yang sama sesuai dengan beberapa metode? Logika saat ini tidak menangani ini.
- Bisakah kita menambahkan immer ?
- Bagaimana jika saya menggunakan tindakan berbasis kelas? Bagaimana saya bisa melewati pembuat tindakan, bukan jenis tindakan?
Semua itu dengan contoh kode tambahan dan contoh ditutupi dengan kelas peredam .
Saya harus mengatakan bahwa menggunakan kelas untuk reduksi bukanlah pemikiran asli. @amcdnl muncul dengan aksi ngrx yang mengagumkan beberapa waktu yang lalu, tapi sepertinya dia sekarang fokus pada NGXS , belum lagi saya ingin mengetik dan decoupling yang lebih ketat dari logika khusus angular. Berikut adalah daftar perbedaan utama antara tindakan peredam dan tindakan ngrx.
Jika Anda menyukai gagasan untuk menggunakan kelas untuk reduksi Anda, Anda mungkin ingin melakukan hal yang sama untuk pembuat tindakan Anda. Lihatlah kelas aksi-fluks .
Semoga Anda menemukan sesuatu yang berguna untuk proyek Anda. Silakan sampaikan umpan balik Anda kepada saya! Saya sangat menghargai kritik dan pertanyaan apa pun.