
Saya ingin berbagi pengalaman menggunakan redux dalam aplikasi perusahaan. Berbicara tentang perangkat lunak perusahaan sebagai bagian dari artikel, saya fokus pada fitur-fitur berikut:
- Pertama, ini adalah volume fungsionalitas. Ini adalah sistem yang telah dikembangkan selama bertahun-tahun, terus membangun modul baru, atau menyulitkan apa yang sudah ada di sana tanpa batas.
- Kedua, sering, jika kita mempertimbangkan bukan layar presentasi, tetapi tempat kerja seseorang, maka sejumlah besar komponen yang terpasang dapat dipasang pada satu halaman.
- Ketiga, kompleksitas logika bisnis. Jika kita ingin mendapatkan aplikasi yang responsif dan menyenangkan untuk digunakan, sebagian besar dari logika harus dilakukan oleh klien.
Dua poin pertama memberlakukan pembatasan pada margin produktivitas. Lebih lanjut tentang ini nanti. Dan sekarang, saya mengusulkan untuk membahas masalah yang Anda temui menggunakan alur kerja redux klasik, mengembangkan sesuatu yang lebih rumit daripada daftar TODO.
Redux klasik
Sebagai contoh, pertimbangkan aplikasi berikut:

Pengguna menggerakkan sajak - mendapat penilaian tentang bakatnya. Kontrol dengan pengenalan ayat dikontrol dan perhitungan kembali penilaian terjadi untuk setiap perubahan. Ada juga tombol di mana teks dengan hasil diatur ulang, dan pesan ditampilkan kepada pengguna yang dapat ia mulai dari awal. Kode sumber di
utas ini.
Organisasi Kode:

Ada dua modul. Lebih tepatnya, satu modul secara langsung adalah poemScoring. Dan akar aplikasi dengan fungsi umum untuk seluruh sistem adalah aplikasi. Di sana kami memiliki informasi tentang pengguna, menampilkan pesan kepada pengguna. Setiap modul memiliki reduksi, aksi, kontrol, dll. Seiring dengan bertambahnya aplikasi, modul-modul baru bertambah banyak.
Rangkaian reduksi, menggunakan redux-immutable, membentuk kondisi sepenuhnya tidak berubah berikut ini:

Cara kerjanya:
1. Kontrol tindakan-pencipta pengiriman:
import at from '../constants/actionTypes'; export function poemTextChange(text) { return function (dispatch, getstate) { dispatch({ type: at.POEM_TYPE, payload: text }); }; }
Konstanta jenis tindakan dipindahkan ke file terpisah. Pertama, kami sangat aman dari kesalahan ketik. Kedua, Intellisense akan tersedia untuk kita.
2. Kemudian datang ke peredam.
import logic from '../logic/poem'; export default function poemScoringReducer(state = Immutable.Map(), action) { switch (action.type) { case at.POEM_TYPE: return logic.onType(state, action.payload); default: return state; } }
Pemrosesan logika dipindahkan ke
fungsi case terpisah. Jika tidak, kode peredam akan dengan cepat menjadi tidak dapat dibaca.
3. Logika pemrosesan klik menggunakan analisis leksikal dan kecerdasan buatan:
export default { onType(state, text) { return state .set('poemText', text) .set('score', this.calcScore(text)); }, calcScore(text) { const score = Math.floor(text.length / 10); return score > 5 ? 5 : score; } };
Dalam kasus tombol "Puisi baru", kami memiliki pencipta aksi berikut:
export function newPoem() { return function (dispatch, getstate) { dispatch({ type: at.POEM_TYPE, payload: '' }); dispatch({ type: appAt.SHOW_MESSAGE, payload: 'You can begin a new poem now!' }); }; }
Pertama, kirim tindakan yang sama dengan yang mengatur ulang teks dan skor kami. Kemudian, kirim tindakan, yang akan ditangkap oleh peredam lain dan menampilkan pesan kepada pengguna.
Semuanya indah. Mari kita buat masalah untuk diri kita sendiri:
Masalahnya:
Kami telah memposting aplikasi kami. Tetapi pengguna kami, melihat bahwa mereka diminta untuk menulis puisi, secara alami mulai memposting karya mereka, yang tidak sesuai dengan standar korporat bahasa puisi. Dengan kata lain, kita perlu memoderasi kata-kata cabul.
Apa yang akan kita lakukan:
- dalam teks input, perlu untuk mengganti semua kata yang tidak berbudaya dengan * disensor *
- selain itu, jika pengguna telah menggerakkan kata kotor, Anda perlu memperingatkannya dengan pesan bahwa ia melakukan kesalahan.
Bagus Kami hanya perlu menganalisis teks, selain menghitung skor, untuk mengganti kata-kata buruk. Tidak masalah Dan juga, untuk memberi tahu pengguna, Anda perlu daftar apa yang kami hapus. Kode sumber ada di
sini .
Kami membuat kembali fungsi logika sehingga, di samping status baru, mengembalikan informasi yang diperlukan untuk pesan kepada pengguna (kata yang diganti):
export default { onType(state, text) { const { reductedText, censoredWords } = this.redactText(text); const newState = state .set('poemText', reductedText) .set('score', this.calcScore(reductedText)); return { newState, censoredWords }; }, calcScore(text) { const score = Math.floor(text.length / 10); return score > 5 ? 5 : score; }, redactText(text) { const result = { reductedText:text }; const censoredWords = []; obscenseWords.forEach((badWord) => { if (result.reductedText.indexOf(badWord) >= 0) { result.reductedText = result.reductedText.replace(badWord, '*censored*'); censoredWords.push(badWord); } }); if (censoredWords.length > 0) { result.censoredWords = censoredWords.join(' ,'); } return result; } };
Mari kita terapkan sekarang. Tapi bagaimana caranya? Dalam peredam, tidak masuk akal bagi kita untuk memanggilnya lagi, karena kita akan meletakkan teks dan evaluasi di negara bagian, tetapi apa yang harus dilakukan dengan pesan? Untuk mengirim pesan, dalam hal apa pun, kami harus mengirim tindakan yang sesuai. Jadi, kami sedang menyelesaikan aksi-pencipta.
export function poemTextChange(text) { return function (dispatch, getState) { const globalState = getState(); const scoringStateOld = globalState.get('poemScoring');
Ini juga diperlukan untuk memodifikasi reducer, karena tidak lagi memanggil fungsi logika:
switch (action.type) { case at.POEM_TYPE: return action.payload; default: return state;
Apa yang terjadi:
Dan sekarang, pertanyaannya adalah. Mengapa kita memerlukan peredam yang, sebagian besar, hanya akan mengembalikan muatan alih-alih negara baru? Ketika tindakan lain muncul yang memproses logika dalam tindakan, apakah perlu mendaftarkan jenis tindakan baru? Atau mungkin membuat satu SET_STATE yang umum? Mungkin tidak, karena itu, inspektur akan berantakan. Jadi kami akan memproduksi kasing yang sama?
Inti dari masalah adalah sebagai berikut. Jika pemrosesan logika melibatkan bekerja dengan sepotong negara, yang bertanggung jawab atas beberapa peredam, maka Anda harus menulis segala macam penyimpangan. Sebagai contoh, hasil antara fungsi kasus, yang kemudian harus tersebar di berbagai reduksi menggunakan beberapa tindakan.
Situasi serupa, jika fungsi case membutuhkan lebih banyak informasi daripada apa yang ada di peredam Anda, Anda harus melakukan seruannya ke tindakan, di mana ada akses ke negara global, diikuti dengan mengirim negara baru sebagai muatan. Pengecil harus dibagi dalam hal apa pun, jika ada banyak logika dalam modul. Dan ini menciptakan ketidaknyamanan yang luar biasa.
Mari kita lihat situasi di satu sisi. Dalam aksi kami, kami mendapat bagian dari dunia. Ini diperlukan untuk
mengubahnya (
globalState.get ('poemScoring'); ). Ternyata kita sudah tahu apa yang terjadi dengan negara bagian itu. Kami memiliki negara bagian baru. Kami tahu di mana harus meletakkannya. Tetapi alih-alih memasukkannya ke dalam global, kami menjalankannya dengan semacam teks konstan di seluruh kaskade pereduksi sehingga melewati setiap sakelar dan menggantinya hanya sekali. Saya dari realisasi ini, keriput. Saya mengerti bahwa ini dilakukan untuk kemudahan pengembangan dan untuk mengurangi konektivitas. Tetapi dalam kasus kami, itu tidak lagi memiliki peran.
Sekarang, saya akan mencantumkan semua poin yang tidak saya sukai dalam implementasi saat ini, jika harus ditingkatkan secara luas dan mendalam untuk waktu yang tidak terbatas :
- Ketidaknyamanan yang signifikan saat bekerja dengan kondisi di luar peredam.
- Masalah pemisahan kode. Setiap kali kami mengirim tindakan, ia akan melewati setiap peredam, melewati setiap kasus. Lebih mudah untuk tidak repot ketika Anda memiliki aplikasi kecil. Tetapi, jika Anda memiliki monster yang dibangun selama beberapa tahun dengan puluhan reduksi dan ratusan case, maka saya mulai berpikir tentang kelayakan pendekatan semacam itu. Mungkin, bahkan dengan ribuan kasus, ini tidak akan berdampak signifikan pada kinerja. Tapi, memahami bahwa ketika mencetak teks, setiap pers akan menyebabkan bagian melalui ratusan kasus, saya tidak bisa membiarkannya apa adanya. Apa pun, kelambanan terkecil, dikalikan dengan tak terhingga, cenderung tak terhingga. Dengan kata lain, jika Anda tidak memikirkan hal-hal seperti itu, cepat atau lambat, masalah akan muncul.
Apa saja pilihannya?
a. Aplikasi yang terisolasi dengan penyedia mereka sendiri. Di setiap modul (sub-aplikasi), Anda harus menduplikasi bagian umum negara bagian (akun, pesan, dll.).
b. Gunakan reduksi asinkron yang dapat dicolokkan . Ini tidak direkomendasikan oleh Dan sendiri.
c. Gunakan filter aksi dalam reduksi. Artinya, setiap pengiriman harus disertai dengan informasi tentang modul mana ia dikirim. Dan di root reducers dari modul, tulis kondisi yang sesuai. Saya sudah mencoba. Tidak ada jumlah kesalahan tidak disengaja baik sebelum atau sesudah. Ada kebingungan konstan tentang ke mana tindakan itu berlangsung. - Setiap kali suatu tindakan dikirimkan, tidak hanya ada lari untuk setiap peredam, tetapi juga koleksi keadaan terbalik. Tidak masalah jika negara telah berubah di reducer - itu akan diganti dalam CombedReducers.
- Setiap pengiriman memaksa mapStateToProps untuk diproses untuk setiap komponen yang terpasang yang dipasang pada halaman. Jika kita membagi reduksi, kita harus membagi pengiriman. Apakah penting bahwa kita memiliki tombol yang menimpa teks dan menampilkan pesan dengan kiriman yang berbeda? Mungkin tidak. Tetapi saya memiliki pengalaman pengoptimalan, ketika mengurangi jumlah pengiriman dari 15 menjadi 3 memungkinkan untuk secara signifikan meningkatkan respons sistem, dengan jumlah yang sama dari logika bisnis yang diproses. Saya tahu bahwa ada perpustakaan yang dapat menggabungkan beberapa pengiriman menjadi satu batch, tetapi ini merupakan perjuangan dengan investigasi menggunakan kruk.
- Saat menghancurkan pengiriman, terkadang sangat sulit untuk melihat apa yang terjadi. Tidak ada satu tempat, semuanya tersebar di file yang berbeda. Penting untuk mencari di mana pemrosesan dilaksanakan dengan mencari konstanta dalam semua kode sumber.
- Dalam kode di atas, komponen dan tindakan mengakses status global secara langsung:
const userName = globalState.getIn(['app', 'account', 'name']); β¦ const text = state.getIn(['poemScoring', 'poemText']);
Ini tidak baik karena beberapa alasan:
a. Modul idealnya harus diisolasi. Mereka tidak perlu tahu di negara bagian mana mereka tinggal.
b. Menyebutkan jalur yang sama di tempat yang berbeda seringkali penuh tidak hanya dengan kesalahan / kesalahan ketik, tetapi juga membuat refactoring sangat sulit jika mengubah konfigurasi negara global, atau mengubah cara penyimpanannya.
- Semakin banyak, saat menulis tindakan baru, saya mendapat kesan bahwa saya sedang menulis kode demi kode. Misalkan kita ingin menambahkan kotak centang ke halaman dan mencerminkan keadaan boolean dalam cerita. Jika kita menginginkan organisasi aksi / reduksi yang seragam, maka kita harus:
- Mendaftar konstan aksi-jenis
- Tulis kawah aksi
- Dalam kontrol, impor dan daftarkan di mapDispatchToProps
- Mendaftar di PropTypes
- Buat handleCheckBoxClick di kontrol dan tentukan di kotak centang
- Tambahkan saklar di peredam dengan panggilan fungsi kasus
- Tulis fungsi case dalam logika
Demi satu cek tinju! - Status yang dihasilkan dengan CombedRers bersifat statis. Tidak masalah apakah Anda sudah masuk modul B atau tidak, bagian ini akan ada dalam cerita. Kosong, tetapi akan. Tidak nyaman menggunakan inspektur saat ada banyak simpul kosong yang tidak digunakan di steet.
Cara kami mencoba menyelesaikan beberapa masalah yang dijelaskan di atas
Jadi, kami mendapat reduksi bodoh, dan dalam action-craters / logic kami menulis footpieces kode untuk bekerja dengan struktur immutable yang tertanam dalam. Untuk menghilangkan ini, saya menggunakan mekanisme penyeleksi hierarkis, yang memungkinkan tidak hanya akses ke bagian negara yang diinginkan, tetapi juga menggantinya (setIn nyaman). Saya menerbitkan ini dalam paket
penyeleksi yang tidak dapat diubah .
Mari kita lihat contoh cara kerjanya (
repositori ):
Dalam modul poemScoring, kami menjelaskan objek penyeleksi. Kami menjelaskan bidang-bidang tersebut dari keadaan yang ingin kami akses langsung baca / tulis. Sarang dan parameter apa pun untuk mengakses elemen koleksi diperbolehkan. Tidak perlu menggambarkan semua bidang yang mungkin ada di artikel kami.
import extendSelectors from 'immutable-selectors'; const selectors = { poemText:{}, score:{} }; extendSelectors(selectors, [ 'poemScoring' ]); export default selectors;
Lebih lanjut, metode extendedSelectors mengubah setiap bidang dalam objek kita menjadi fungsi pemilih. Parameter kedua menunjukkan jalur ke bagian negara bagian yang dikendalikan pemilih. Kami tidak membuat objek baru, tetapi mengubah objek saat ini. Ini memberi kami bonus dalam bentuk kecerdasan kerja:

Apa objek kami - pemilih setelah ekspansi:

Fungsi
selectors.poemText (state) cukup menjalankan
state.getIn (['poemScoring', 'poemText']) .
Function
root (state) - mendapat 'poemScoring'.
Setiap pemilih memiliki fungsi
ganti sendiri
(globalState, newPart) , yang melalui setIn mengembalikan negara global baru dengan bagian yang sesuai diganti.
Juga, objek
datar ditambahkan ke mana semua kunci pemilih unik digandakan. Artinya, jika kita menggunakan kondisi yang dalam
selectors = { dive:{ in:{ to:{ the:{ deep:{} } } } }}
Anda bisa menjadi lebih dalam sebagai
selectors.dive.in.to.the.deep (state) atau sebagai
selectors.flat.deep (state) .
Silakan. Kami perlu memperbarui akuisisi data di kontrol:
Puisi:
function mapStateToProps(state, ownprops) { return { text:selectors.poemText(state) || '' }; }
Skor:
function mapStateToProps(state, ownprops) { const score = selectors.score(state); return { score }; }
Selanjutnya, ubah peredam root:
import initialState from './initialState'; function setStateReducer(state = initialState, action) { if (action.setState) { return action.setState; } else { return state;
Jika diinginkan, kita dapat menggabungkan menggunakan CombedReducers.
Action crater, misalnya, poemTextChange:
export function poemTextChange(text) { return function (dispatch, getState) { dispatch({ type: 'Poem typing', setState: logic.onType(getState(), text), payload: text }); }; }
Kita tidak bisa lagi menggunakan konstanta tipe tindakan, karena tipe sekarang hanya digunakan untuk visualisasi di inspektur. Kami di proyek menulis deskripsi teks lengkap tentang tindakan dalam bahasa Rusia. Anda juga dapat membuang muatan, tetapi saya mencoba menyimpannya sehingga di inspektur, jika perlu, saya mengerti dengan parameter apa tindakan itu dipanggil.
Dan, faktanya, logikanya sendiri:
onType(gState, text) { const { reductedText, censoredWords } = this.redactText(text); const poemState = selectors.root(gState) || Immutable.Map();
Pada saat yang sama,
message.showMessage diimpor dari logika modul tetangga, yang menjelaskan pemilihnya:
showMessage(gState, text) { return selectors.message.text.replace(gState, text); }.
Apa yang ternyata:

Perhatikan bahwa kami memiliki satu pengiriman, data dalam dua modul berubah.
Semua ini memungkinkan kami untuk menyingkirkan reduksi dan konstanta tipe tindakan, serta menyelesaikan atau mengatasi sebagian besar hambatan yang diuraikan di atas.
Bagaimana lagi ini bisa diterapkan?
Pendekatan ini nyaman digunakan ketika perlu untuk memastikan bahwa kontrol atau modul Anda memberikan pekerjaan dengan berbagai keadaan. Katakanlah satu puisi saja tidak cukup untuk kita. Kami ingin pengguna dapat membuat puisi pada dua tab yang berbeda dalam disiplin ilmu yang berbeda (anak-anak, romantis). Dalam hal ini, kami tidak dapat mengimpor penyeleksi dalam logika / kontrol, tetapi menentukannya sebagai parameter dalam kontrol eksternal:
<Poem selectors = {selectors.hildPoem}/> <Poem selectors = {selectors.romanticPoem}/>
Dan, lebih lanjut, berikan parameter ini ke kawah aksi. Ini cukup untuk membuat kombinasi kompleks dari komponen dan logika tertutup sepenuhnya, sehingga mudah digunakan kembali.
Keterbatasan saat menggunakan pemilih-tidak berubah:Ini tidak akan berfungsi untuk menggunakan kunci di negara "nama", karena untuk fungsi induk akan ada upaya untuk menimpa properti yang dipesan.
Apa hasilnya
Akibatnya, pendekatan yang agak fleksibel diperoleh, hubungan kode implisit oleh konstanta teks dihilangkan, overhead dikurangi sambil menjaga kenyamanan pengembangan. Ada juga inspektur redux yang berfungsi penuh dengan kemungkinan perjalanan waktu. Saya tidak punya keinginan untuk kembali ke reduksi standar.
Secara umum, itu saja. Terima kasih untuk waktu anda Mungkin seseorang akan tertarik untuk mencobanya!