Redux Toolkit sebagai Alat untuk Pengembangan Redux yang Efektif

gambar
Saat ini, bagian terbesar dari aplikasi web berdasarkan kerangka Bereaksi sedang dikembangkan menggunakan perpustakaan Redux. Perpustakaan ini adalah implementasi paling populer dari arsitektur FLUX dan, meskipun ada beberapa keuntungan nyata, ia memiliki kelemahan yang sangat signifikan, seperti:


  • kompleksitas dan "verbositas" dari pola yang direkomendasikan untuk penulisan dan pengorganisasian kode, yang memerlukan sejumlah besar boilerplate;
  • kurangnya kontrol bawaan untuk perilaku asinkron dan efek samping, yang mengarah pada kebutuhan untuk memilih alat yang tepat dari berbagai add-on yang ditulis oleh pengembang pihak ketiga.

Untuk mengatasi kekurangan ini, pengembang Redux memperkenalkan pustaka Redux Toolkit. Alat ini adalah seperangkat solusi praktis dan metode yang dirancang untuk menyederhanakan pengembangan aplikasi menggunakan Redux. Pengembang perpustakaan ini bertujuan untuk menyederhanakan kasus khas penggunaan Redux. Alat ini bukan solusi universal dalam setiap kemungkinan kasus penggunaan Redux, tetapi memungkinkan Anda untuk menyederhanakan kode yang perlu ditulis pengembang.


Pada artikel ini, kita akan berbicara tentang alat utama yang termasuk dalam Redux Toolkit, dan juga, dengan menggunakan contoh fragmen aplikasi internal kita, menunjukkan cara menggunakannya dalam kode yang ada.


Secara singkat tentang perpustakaan


Ringkasan Redux Toolkit:


  • sebelum dirilis, perpustakaan itu disebut redux-starter-kit;
  • rilis berlangsung pada akhir Oktober 2019;
  • Perpustakaan secara resmi didukung oleh pengembang Redux.

Menurut pengembang , Redux Toolkit melakukan fungsi-fungsi berikut:


  • Membantu Anda memulai dengan cepat menggunakan Redux.
  • menyederhanakan pekerjaan dengan tugas-tugas khas dan kode Redux;
  • memungkinkan Anda untuk menggunakan praktik terbaik Redux secara default;
  • menawarkan solusi yang mengurangi rasa tidak percaya pada pelat ketel.

Redux Toolkit menyediakan serangkaian alat yang dirancang khusus dan menambahkan sejumlah alat yang telah terbukti baik yang biasa digunakan bersama dengan Redux. Pendekatan ini memungkinkan pengembang untuk memutuskan bagaimana dan alat apa yang digunakan dalam aplikasi mereka. Dalam perjalanan artikel ini, kami akan mencatat pinjaman mana yang digunakan perpustakaan ini. Untuk informasi lebih lanjut dan dependensi Redux Toolkit, lihat deskripsi paket @ reduxjs / toolkit .


Fitur paling signifikan yang disediakan oleh pustaka Redux Toolkit adalah:


  • #configureStore - fungsi yang dirancang untuk menyederhanakan proses pembuatan dan konfigurasi penyimpanan;
  • #createReducer - fungsi yang membantu menjelaskan dan membuat peredam secara ringkas dan jelas;
  • #createAction - mengembalikan fungsi pembuat aksi untuk string yang ditentukan dari jenis aksi;
  • #createSlice - menggabungkan fungsi createAction dan createReducer;
  • createSelector adalah fungsi dari pustaka pilih kembali , diekspor kembali untuk kemudahan penggunaan.

Perlu juga dicatat bahwa Redux Toolkit sepenuhnya terintegrasi dengan TypeScript. Untuk informasi lebih lanjut, lihat bagian Penggunaan Dengan TypeScript dari dokumentasi resmi.


Aplikasi


Pertimbangkan untuk menggunakan pustaka Redux Toolkit sebagai contoh fragmen aplikasi React Redux yang benar-benar digunakan.
Catatan Lebih lanjut dalam artikel ini, kode sumber akan disajikan baik tanpa menggunakan Redux Toolkit dan menggunakannya, yang akan memungkinkan untuk mengevaluasi dengan lebih baik aspek positif dan negatif dari penggunaan perpustakaan ini.


Tantangan


Di salah satu aplikasi internal kami, ada kebutuhan untuk menambah, mengedit, dan menampilkan informasi tentang rilis produk perangkat lunak kami. Untuk masing-masing tindakan ini, fungsi API terpisah dikembangkan, yang hasilnya harus ditambahkan ke toko Redux. Sebagai cara mengendalikan perilaku asinkron dan efek samping, kami akan menggunakan Thunk .


Pembuatan penyimpanan


Versi awal kode sumber yang membuat repositori tampak seperti ini:


import { createStore, applyMiddleware, combineReducers, compose, } from 'redux'; import thunk from 'redux-thunk'; import * as reducers from './reducers'; const ext = window.__REDUX_DEVTOOLS_EXTENSION__; const devtoolMiddleware = ext && process.env.NODE_ENV === 'development' ? ext() : f => f; const store = createStore( combineReducers({ ...reducers, }), compose( applyMiddleware(thunk), devtoolMiddleware ) ); 

Jika Anda hati-hati melihat kode di atas, Anda dapat melihat urutan tindakan yang agak panjang yang harus diselesaikan agar penyimpanannya terkonfigurasi penuh. Redux Toolkit berisi alat yang dirancang untuk menyederhanakan prosedur ini, yaitu fungsi configureStore.


Fungsi ConfigureStore


Alat ini memungkinkan Anda untuk secara otomatis menggabungkan reduksi, menambahkan Redux middleware (default termasuk redux-thunk), dan juga menggunakan ekstensi Redux DevTools. Fungsi configureStore menerima objek dengan properti berikut sebagai parameter input:


  • peredam - satu set peredam khusus,
  • middleware - parameter opsional yang menentukan array middleware yang dirancang untuk terhubung ke repositori,
  • devTools - parameter tipe logis yang memungkinkan Anda mengaktifkan ekstensi Redux DevTools yang terinstal di browser (nilai defaultnya benar),
  • preloadedState - parameter opsional yang menetapkan status awal repositori,
  • enhancer - parameter opsional yang mendefinisikan satu set amplifier.

Untuk mendapatkan daftar middleware paling populer, Anda dapat menggunakan fungsi khusus getDefaultMiddleware, yang juga merupakan bagian dari Redux Toolkit. Fungsi ini mengembalikan array dengan middleware diaktifkan secara default di pustaka Redux Toolkit. Daftar middlewares ini berbeda tergantung pada mode di mana kode Anda dieksekusi. Dalam mode produksi, sebuah array hanya terdiri dari satu elemen - thunk. Dalam mode pengembangan, pada saat penulisan, daftar diisi ulang dengan middleware berikut:


  • serializableStateInvariant - alat yang dikembangkan secara khusus untuk digunakan dalam Redux Toolkit dan dirancang untuk memeriksa pohon status keberadaan nilai-nilai non-serializable, seperti fungsi, Janji, Simbol, dan nilai-nilai lain yang bukan data JS sederhana;
  • immutableStateInvariant - middleware dari paket redux-immutable-state-invariant , yang dirancang untuk mendeteksi mutasi pada data yang disimpan dalam repositori.

Untuk menentukan daftar naik dari middleware, fungsi getDefaultMidlleware menerima objek yang mendefinisikan daftar middleware termasuk dan pengaturan untuk masing-masing. Informasi lebih lanjut tentang informasi ini dapat ditemukan di bagian yang sesuai dari dokumentasi resmi.


Sekarang kami akan menulis ulang bagian kode yang bertanggung jawab untuk membuat repositori menggunakan alat yang dijelaskan di atas. Hasilnya, kami mendapatkan yang berikut:


 import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; import * as reducers from './reducers'; const middleware = getDefaultMiddleware({ immutableCheck: false, serializableCheck: false, thunk: true, }); export const store = configureStore({ reducer: { ...reducers }, middleware, devTools: process.env.NODE_ENV !== 'production', }); 

Dengan menggunakan contoh bagian kode ini, Anda dapat dengan jelas melihat bahwa fungsi configureStore memecahkan masalah berikut:


  • kebutuhan untuk menggabungkan reduksi, secara otomatis memanggil CombedReducers,
  • kebutuhan untuk menggabungkan middleware, secara otomatis memanggil applyMiddleware.

Ini juga memungkinkan Anda untuk lebih mudah mengaktifkan ekstensi Redux DevTools menggunakan fungsi composeWithDevTools dari paket redux-devtools-extension . Semua hal di atas menunjukkan bahwa menggunakan fungsi ini memungkinkan Anda untuk membuat kode lebih kompak dan mudah dipahami.


Ini melengkapi pembuatan dan konfigurasi penyimpanan. Kami mentransfernya ke penyedia dan melanjutkan.


Tindakan, pencipta aksi, dan peredam


Sekarang mari kita lihat fitur-fitur Redux Toolkit dalam hal mengembangkan tindakan, pembuat tindakan, dan peredam. Versi awal kode tanpa menggunakan Redux Toolkit disusun sebagai file actions.js dan reducers.js. Isi file action.js tampak seperti ini:


 import * as productReleasesService from '../../services/productReleases'; export const PRODUCT_RELEASES_FETCHING = 'PRODUCT_RELEASES_FETCHING'; export const PRODUCT_RELEASES_FETCHED = 'PRODUCT_RELEASES_FETCHED'; export const PRODUCT_RELEASES_FETCHING_ERROR = 'PRODUCT_RELEASES_FETCHING_ERROR'; … export const PRODUCT_RELEASE_UPDATING = 'PRODUCT_RELEASE_UPDATING'; export const PRODUCT_RELEASE_UPDATED = 'PRODUCT_RELEASE_UPDATED'; export const PRODUCT_RELEASE_CREATING_UPDATING_ERROR = 'PRODUCT_RELEASE_CREATING_UPDATING_ERROR'; function productReleasesFetching() { return { type: PRODUCT_RELEASES_FETCHING }; } function productReleasesFetched(productReleases) { return { type: PRODUCT_RELEASES_FETCHED, productReleases }; } function productReleasesFetchingError(error) { return { type: PRODUCT_RELEASES_FETCHING_ERROR, error } } … export function fetchProductReleases() { return dispatch => { dispatch(productReleasesFetching()); return productReleasesService.getProductReleases().then( productReleases => dispatch(productReleasesFetched(productReleases)) ).catch(error => { error.clientMessage = "Can't get product releases"; dispatch(productReleasesFetchingError(error)) }); } } … export function updateProductRelease( id, productName, productVersion, releaseDate ) { return dispatch => { dispatch(productReleaseUpdating()); return productReleasesService.updateProductRelease( id, productName, productVersion, releaseDate ).then( productRelease => dispatch(productReleaseUpdated(productRelease)) ).catch(error => { error.clientMessage = "Can't update product releases"; dispatch(productReleaseCreatingUpdatingError(error)) }); } } 

Isi file reducers.js sebelum menggunakan Redux Toolkit:


 const initialState = { productReleases: [], loadedProductRelease: null, fetchingState: 'none', creatingState: 'none', updatingState: 'none', error: null, }; export default function reducer(state = initialState, action = {}) { switch (action.type) { case productReleases.PRODUCT_RELEASES_FETCHING: return { ...state, fetchingState: 'requesting', error: null, }; case productReleases.PRODUCT_RELEASES_FETCHED: return { ...state, productReleases: action.productReleases, fetchingState: 'success', }; case productReleases.PRODUCT_RELEASES_FETCHING_ERROR: return { ...state, fetchingState: 'failed', error: action.error }; … case productReleases.PRODUCT_RELEASE_UPDATING: return { ...state, updatingState: 'requesting', error: null, }; case productReleases.PRODUCT_RELEASE_UPDATED: return { ...state, updatingState: 'success', productReleases: state.productReleases.map(productRelease => { if (productRelease.id === action.productRelease.id) return action.productRelease; return productRelease; }) }; case productReleases.PRODUCT_RELEASE_UPDATING_ERROR: return { ...state, updatingState: 'failed', error: action.error }; default: return state; } } 

Seperti yang dapat kita lihat, di sinilah sebagian besar pelat tungku terkandung: konstanta jenis tindakan, pembuat tindakan, konstanta lagi, tetapi dalam kode peredam, dibutuhkan waktu untuk menulis semua kode ini. Anda dapat menyingkirkan sebagian boilerplate ini dengan menggunakan fungsi createAction dan createReducer, yang juga merupakan bagian dari Redux Toolkit.


Fungsi CreateAction


Di bagian kode yang diberikan, metode standar untuk mendefinisikan suatu tindakan di Redux digunakan: pertama, konstanta didefinisikan secara terpisah yang menentukan jenis tindakan, dan kemudian - fungsi pencipta aksi jenis ini. Fungsi createAction menggabungkan dua deklarasi ini menjadi satu. Pada input, dibutuhkan jenis tindakan dan mengembalikan pembuat tindakan untuk jenis itu. Pembuat tindakan dapat dipanggil baik tanpa argumen, atau dengan beberapa argumen (payload), yang nilainya akan ditempatkan di bidang payload dari tindakan yang dibuat. Selain itu, pembuat tindakan menimpa fungsi toString (), sehingga jenis tindakan menjadi representasi string-nya.


Dalam beberapa kasus, Anda mungkin perlu menulis logika tambahan untuk menyesuaikan nilai payload, misalnya, menerima beberapa parameter untuk pembuat tindakan, membuat pengidentifikasi acak, atau mendapatkan cap waktu saat ini. Untuk melakukan ini, createAction mengambil argumen kedua opsional - fungsi yang akan digunakan untuk memperbarui nilai payload. Informasi lebih lanjut tentang parameter ini dapat ditemukan di dokumentasi resmi.
Menggunakan fungsi createAction, kami mendapatkan kode berikut:


 export const productReleasesFetching = createAction('PRODUCT_RELEASES_FETCHING'); export const productReleasesFetched = createAction('PRODUCT_RELEASES_FETCHED'); export const productReleasesFetchingError = createAction('PRODUCT_RELEASES_FETCHING_ERROR'); … export function fetchProductReleases() { return dispatch => { dispatch(productReleasesFetching()); return productReleasesService.getProductReleases().then( productReleases => dispatch(productReleasesFetched({ productReleases })) ).catch(error => { error.clientMessage = "Can't get product releases"; dispatch(productReleasesFetchingError({ error })) }); } } ... 

Fungsi CreateReducer


Sekarang pertimbangkan peredamnya. Seperti dalam contoh kami, reduksi sering diimplementasikan menggunakan pernyataan switch, dengan satu register untuk setiap jenis tindakan diproses. Pendekatan ini bekerja dengan baik, tetapi bukan tanpa boilerplate dan rawan kesalahan. Misalnya, mudah lupa untuk menggambarkan kasus default atau tidak untuk mengatur keadaan awal. Fungsi createReducer menyederhanakan pembuatan fungsi reducer dengan mendefinisikannya sebagai tabel pencarian fungsi untuk memproses setiap jenis tindakan. Ini juga memungkinkan Anda untuk secara signifikan menyederhanakan logika pembaruan yang tidak berubah dengan menulis kode dengan gaya "bisa berubah" di dalam reduksi.


Gaya penanganan acara yang kuat tersedia melalui penggunaan perpustakaan Immer . Fungsi pawang dapat "mengubah" keadaan yang dilewatkan untuk mengubah properti, atau mengembalikan status baru, seperti ketika bekerja dalam gaya yang tidak dapat diubah, tetapi berkat Immer, mutasi objek yang sebenarnya tidak dilakukan. Opsi pertama jauh lebih mudah untuk bekerja dan persepsi, terutama ketika mengubah objek dengan sarang yang dalam.


Hati-hati: mengembalikan objek baru dari fungsi menimpa perubahan "bisa berubah". Penggunaan kedua metode pembaruan status secara simultan tidak akan berfungsi.


Fungsi createReducer menerima argumen berikut sebagai parameter input:


  • kondisi penyimpanan awal
  • objek yang membangun korespondensi antara jenis tindakan dan reduksi, yang masing-masing memproses jenis tertentu.

Menggunakan metode createReducer, kita mendapatkan kode berikut:


 const initialState = { productReleases: [], loadedProductRelease: null, fetchingState: 'none', creatingState: 'none', loadingState: 'none', error: null, }; const counterReducer = createReducer(initialState, { [productReleasesFetching]: (state, action) => { state.fetchingState = 'requesting' }, [productReleasesFetched.type]: (state, action) => { state.productReleases = action.payload.productReleases; state.fetchingState = 'success'; }, [productReleasesFetchingError]: (state, action) => { state.fetchingState = 'failed'; state.error = action.payload.error; }, … [productReleaseUpdating]: (state) => { state.updatingState = 'requesting' }, [productReleaseUpdated]: (state, action) => { state.updatingState = 'success'; state.productReleases = state.productReleases.map(productRelease => { if (productRelease.id === action.payload.productRelease.id) return action.payload.productRelease; return productRelease; }); }, [productReleaseUpdatingError]: (state, action) => { state.updating = 'failed'; state.error = action.payload.error; }, }); 

Seperti yang dapat kita lihat, menggunakan fungsi createAction dan createReducer pada dasarnya memecahkan masalah penulisan kode tambahan, tetapi masalah membuat konstanta sebelumnya masih tetap ada. Oleh karena itu, kami mempertimbangkan opsi yang lebih kuat yang menggabungkan generasi pembuat tindakan dan peredam - fungsi createSlice.


Fungsi CreateSlice


Fungsi createSlice menerima objek dengan bidang berikut sebagai parameter input:


  • name - namespace dari tindakan yang dibuat ( ${name}/${action.type} );
  • initialState - keadaan awal peredam;
  • reduksi - objek dengan penangan. Setiap pawang mengambil fungsi dengan argumen negara dan tindakan, tindakan berisi data di properti payload dan nama acara di properti nama. Selain itu, dimungkinkan untuk mengubah terlebih dahulu data yang diterima dari acara sebelum memasuki peredam (misalnya, tambahkan id ke elemen koleksi). Untuk melakukan ini, alih-alih suatu fungsi, Anda harus melewati objek dengan peredam dan menyiapkan bidang, di mana peredam adalah fungsi pengendali tindakan dan persiapan adalah fungsi penangan muatan yang mengembalikan muatan yang diperbarui;
  • extraReducers - sebuah objek yang mengandung reduksi dari irisan lain. Parameter ini mungkin diperlukan jika perlu memperbarui objek milik irisan lain. Anda dapat mempelajari lebih lanjut tentang fungsi ini dari bagian yang sesuai dari dokumentasi resmi.

Hasil dari fungsi adalah objek yang disebut "slice", dengan bidang-bidang berikut:


  • nama - nama irisan,
  • peredam - peredam,
  • tindakan - serangkaian tindakan.

Menggunakan fungsi ini untuk menyelesaikan masalah kami, kami mendapatkan kode sumber berikut:


 const initialState = { productReleases: [], loadedProductRelease: null, fetchingState: 'none', creatingState: 'none', loadingState: 'none', error: null, }; const productReleases = createSlice({ name: 'productReleases', initialState, reducers: { productReleasesFetching: (state) => { state.fetchingState = 'requesting'; }, productReleasesFetched: (state, action) => { state.productReleases = action.payload.productReleases; state.fetchingState = 'success'; }, productReleasesFetchingError: (state, action) => { state.fetchingState = 'failed'; state.error = action.payload.error; }, … productReleaseUpdating: (state) => { state.updatingState = 'requesting' }, productReleaseUpdated: (state, action) => { state.updatingState = 'success'; state.productReleases = state.productReleases.map(productRelease => { if (productRelease.id === action.payload.productRelease.id) return action.payload.productRelease; return productRelease; }); }, productReleaseUpdatingError: (state, action) => { state.updating = 'failed'; state.error = action.payload.error; }, }, }); 

Sekarang kita akan mengekstrak pembuat tindakan dan peredam dari irisan yang dibuat.


 const { actions, reducer } = productReleases; export const { productReleasesFetched, productReleasesFetching, productReleasesFetchingError, … productReleaseUpdated, productReleaseUpdating, productReleaseUpdatingError } = actions; export default reducer; 

Kode sumber pembuat tindakan yang berisi panggilan API tidak berubah, kecuali metode meneruskan parameter saat mengirim tindakan:


 export const fetchProductReleases = () => (dispatch) => { dispatch(productReleasesFetching()); return productReleasesService .getProductReleases() .then((productReleases) => dispatch(productReleasesFetched({ productReleases }))) .catch((error) => { error.clientMessage = "Can't get product releases"; dispatch(productReleasesFetchingError({ error })); }); }; … export const updateProductRelease = (id, productName, productVersion, releaseDate) => (dispatch) => { dispatch(productReleaseUpdating()); return productReleasesService .updateProductRelease(id, productName, productVersion, releaseDate) .then((productRelease) => dispatch(productReleaseUpdated({ productRelease }))) .catch((error) => { error.clientMessage = "Can't update product releases"; dispatch(productReleaseUpdatingError({ error })); }); 

Kode di atas menunjukkan bahwa fungsi createSlice memungkinkan Anda untuk menyingkirkan bagian penting dari boilerplate saat bekerja dengan Redux, yang memungkinkan Anda untuk tidak hanya membuat kode lebih ringkas, ringkas dan mudah dipahami, tetapi juga menghabiskan lebih sedikit waktu untuk menulisnya.


Ringkasan


Pada akhir artikel ini, saya ingin mengatakan bahwa terlepas dari kenyataan bahwa pustaka Redux Toolkit tidak menambahkan sesuatu yang baru ke manajemen penyimpanan, ia menyediakan sejumlah cara yang jauh lebih nyaman untuk menulis kode daripada sebelumnya. Alat-alat ini memungkinkan tidak hanya untuk membuat proses pengembangan lebih mudah, dimengerti dan lebih cepat, tetapi juga lebih efektif, karena kehadiran sejumlah alat yang telah terbukti baik di perpustakaan. Kami, Inobitek, berencana untuk terus menggunakan perpustakaan ini dalam pengembangan produk perangkat lunak kami dan untuk memantau perkembangan baru yang menjanjikan di bidang teknologi Web.


Terima kasih atas perhatian anda Semoga artikel kami bermanfaat. Informasi lebih lanjut tentang perpustakaan Redux Toolkit dapat diperoleh dari dokumentasi resmi.

Source: https://habr.com/ru/post/id481288/


All Articles