Kamu tidak.
Tapi ... Anda harus menggunakan redux-saga :)
Jawaban Dan Abramov benar tentang redux-thunk
tetapi saya akan berbicara sedikit tentang redux-saga yang sangat mirip tetapi lebih kuat.
Imperatif VS deklaratif
- DOM : jQuery adalah keharusan / Bereaksi adalah deklaratif
- Monads : IO sangat penting / Gratis adalah deklaratif
- Efek redux :
redux-thunk
sangat penting / redux-saga
bersifat deklaratif
Ketika Anda memiliki pukulan di tangan Anda, seperti monad IO atau janji, Anda tidak dapat dengan mudah tahu apa yang akan dilakukan setelah Anda mengeksekusi. Satu-satunya cara untuk menguji seorang thunk adalah dengan mengeksekusinya, dan mengejek dispatcher (atau seluruh dunia luar jika berinteraksi dengan lebih banyak barang ...).
Jika Anda menggunakan tiruan, maka Anda tidak melakukan pemrograman fungsional.
Dilihat melalui lensa efek samping, ejekan adalah bendera yang kode Anda tidak murni, dan di mata programmer fungsional, bukti bahwa ada sesuatu yang salah. Alih-alih mengunduh perpustakaan untuk membantu kami memeriksa gunung es yang masih utuh, kita harus berlayar mengitarinya. Seorang pria TDD / Java hardcore pernah bertanya kepada saya bagaimana Anda mengejek Clojure. Jawabannya, kita biasanya tidak. Kami biasanya melihatnya sebagai tanda kami perlu memperbaiki kode kami.
Sumber
Kisah-kisah (ketika mereka diterapkan di redux-saga
) adalah deklaratif dan seperti komponen Free monad atau Bereaksi, mereka lebih mudah untuk menguji tanpa mock.
Lihat juga artikel ini :
dalam FP modern, kita seharusnya tidak menulis program - kita harus menulis deskripsi program, yang kemudian kita dapat introspeksi, mengubah, dan menafsirkan sesuka hati.
(Sebenarnya, Redux-saga seperti hibrida: aliran sangat penting tetapi efeknya deklaratif)
Kebingungan: tindakan / acara / perintah ...
Ada banyak kebingungan di dunia frontend tentang bagaimana beberapa konsep backend seperti CQRS / EventSourcing dan Flux / Redux mungkin terkait, sebagian besar karena di Flux kita menggunakan istilah "aksi" yang kadang-kadang dapat mewakili kode imperatif ( LOAD_USER
) dan peristiwa ( USER_LOADED
). Saya percaya bahwa seperti event-sourcing, Anda hanya boleh mengirim acara.
Menggunakan saga dalam praktik
Bayangkan sebuah aplikasi dengan tautan ke profil pengguna. Cara idiomatis untuk menangani ini dengan setiap middleware adalah:
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Kisah ini diterjemahkan menjadi:
setiap kali nama pengguna diklik, ambil profil pengguna lalu kirim acara dengan profil yang dimuat.
Seperti yang Anda lihat, ada beberapa kelebihan redux-saga
.
Penggunaan takeLatest
izin untuk menyatakan bahwa Anda hanya tertarik untuk mendapatkan data nama pengguna terakhir yang diklik (menangani masalah konkurensi jika pengguna mengklik sangat cepat pada banyak nama pengguna). Hal-hal semacam ini sulit dengan thunks. Anda bisa menggunakan takeEvery
jika Anda tidak ingin perilaku ini.
Anda menjaga pembuat aksi tetap murni. Perhatikan bahwa masih berguna untuk menyimpan actionCreators (dalam kisah put
dan komponen dispatch
), karena ini dapat membantu Anda untuk menambahkan validasi tindakan (pernyataan / aliran / naskah naskah) di masa depan.
Kode Anda menjadi jauh lebih dapat diuji karena efeknya deklaratif
Anda tidak perlu lagi memicu panggilan seperti rpc seperti actions.loadUser()
. UI Anda hanya perlu mengirimkan apa yang TELAH TERJADI. Kami hanya memecat acara (selalu dalam bentuk lampau!) Dan bukan tindakan lagi. Ini berarti bahwa Anda dapat membuat "bebek" yang dipisahkan atau Konteks Terbatas dan bahwa saga dapat bertindak sebagai titik penghubung antara komponen-komponen modular ini.
Ini berarti bahwa pandangan Anda lebih mudah dikelola karena mereka tidak perlu lagi memuat lapisan terjemahan antara apa yang telah terjadi dan apa yang seharusnya terjadi sebagai efek
Misalnya bayangkan tampilan gulir yang tak terbatas. CONTAINER_SCROLLED
dapat menyebabkan NEXT_PAGE_LOADED
, tetapi apakah itu benar-benar tanggung jawab wadah yang dapat digulir untuk memutuskan apakah kita harus memuat halaman lain? Kemudian dia harus mengetahui hal-hal yang lebih rumit seperti apakah halaman terakhir berhasil dimuat atau tidak, atau apakah sudah ada halaman yang mencoba memuat, atau jika tidak ada lagi item yang tersisa untuk dimuat? Saya tidak berpikir begitu: untuk dapat digunakan kembali secara maksimal, wadah yang dapat digulir harus menggambarkan bahwa itu telah digulir. Pemuatan halaman adalah "efek bisnis" dari gulungan itu
Beberapa mungkin berpendapat bahwa generator secara inheren dapat menyembunyikan keadaan di luar toko redux dengan variabel lokal, tetapi jika Anda mulai mengatur hal-hal kompleks di dalam thunks dengan memulai timer dll Anda akan memiliki masalah yang sama pula. Dan ada select
efek yang sekarang memungkinkan untuk mendapatkan beberapa status dari toko Redux Anda.
Sagas dapat dilalui dengan waktu dan juga memungkinkan pencatatan aliran yang rumit dan perangkat pengembangan yang saat ini sedang dikerjakan. Berikut adalah beberapa pendataan alur async sederhana yang sudah diterapkan:
Decoupling
Sagas tidak hanya menggantikan redux thunks. Mereka datang dari backend / sistem terdistribusi / event-sourcing.
Ini adalah kesalahpahaman yang sangat umum bahwa kisah-kisah hanya ada di sini untuk menggantikan redux thunks Anda dengan testability yang lebih baik. Sebenarnya ini hanyalah detail implementasi dari redux-saga. Menggunakan efek deklaratif lebih baik daripada thunks untuk testability, tetapi pola saga dapat diimplementasikan di atas kode imperatif atau deklaratif.
Pertama-tama, saga adalah bagian dari perangkat lunak yang memungkinkan untuk mengoordinasikan transaksi yang telah berjalan lama (konsistensi akhirnya), dan transaksi di berbagai konteks terbatas (jargon desain berbasis domain).
Untuk menyederhanakan ini untuk dunia frontend, bayangkan ada widget1 dan widget2. Ketika beberapa tombol pada widget1 diklik, maka itu akan memiliki efek pada widget2. Alih-alih menggabungkan 2 widget bersama-sama (yaitu widget1 mengirimkan tindakan yang menargetkan widget2), widget1 hanya mengirimkan bahwa tombolnya diklik. Kemudian hikayat mendengarkan tombol ini klik dan kemudian perbarui widget2 dengan membuang acara baru yang diketahui widget2.
Ini menambah tingkat tipuan yang tidak perlu untuk aplikasi sederhana, tetapi membuatnya lebih mudah untuk skala aplikasi yang kompleks. Anda sekarang dapat mempublikasikan widget1 dan widget2 ke repositori npm yang berbeda sehingga mereka tidak perlu tahu tentang satu sama lain, tanpa harus berbagi registri global tindakan. 2 widget tersebut sekarang dibatasi konteks yang dapat hidup secara terpisah. Mereka tidak membutuhkan satu sama lain untuk konsisten dan dapat digunakan kembali di aplikasi lain juga. Saga adalah titik penghubung antara dua widget yang mengoordinasikannya dengan cara yang berarti untuk bisnis Anda.
Beberapa artikel bagus tentang cara membuat struktur aplikasi Redux Anda, di mana Anda dapat menggunakan Redux-saga untuk alasan decoupling:
Sebuah usecase konkret: sistem notifikasi
Saya ingin komponen saya dapat memicu tampilan notifikasi dalam aplikasi. Tetapi saya tidak ingin komponen saya sangat digabungkan ke sistem pemberitahuan yang memiliki aturan bisnis sendiri (maks. 3 pemberitahuan ditampilkan pada saat yang sama, antrian pemberitahuan, waktu tampilan 4 detik dll ...).
Saya tidak ingin komponen JSX saya memutuskan kapan pemberitahuan akan ditampilkan / disembunyikan. Saya hanya memberikannya kemampuan untuk meminta pemberitahuan, dan meninggalkan aturan yang rumit di dalam saga. Hal-hal semacam ini cukup sulit diimplementasikan dengan pukulan atau janji.
Saya telah menjelaskan di sini bagaimana ini bisa dilakukan dengan saga
Mengapa itu disebut Saga?
Istilah saga berasal dari dunia backend. Saya awalnya memperkenalkan Yassine (penulis Redux-saga) untuk istilah itu dalam diskusi panjang .
Awalnya, istilah itu diperkenalkan dengan kertas , pola saga seharusnya digunakan untuk menangani konsistensi akhirnya dalam transaksi terdistribusi, tetapi penggunaannya telah diperluas ke definisi yang lebih luas oleh pengembang backend sehingga sekarang juga mencakup "manajer proses" pola (entah bagaimana pola kisah asli adalah bentuk khusus dari manajer proses).
Saat ini, istilah "saga" membingungkan karena dapat menggambarkan 2 hal yang berbeda. Seperti yang digunakan dalam redux-saga, itu tidak menjelaskan cara untuk menangani transaksi terdistribusi tetapi cara untuk mengoordinasikan tindakan di aplikasi Anda. redux-saga
bisa juga dipanggil redux-process-manager
.
Lihat juga:
Alternatif
Jika Anda tidak menyukai gagasan menggunakan generator tetapi Anda tertarik dengan pola saga dan sifat decouplingnya, Anda juga dapat mencapai hal yang sama dengan redux-observable yang menggunakan nama epic
untuk menggambarkan pola yang sama persis, tetapi dengan RxJS. Jika Anda sudah terbiasa dengan Rx, Anda akan merasa seperti di rumah.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Beberapa sumber daya yang berguna redux-saga
2017 menyarankan
- Jangan terlalu sering menggunakan Redux-saga hanya untuk menggunakannya. Hanya panggilan API yang dapat diuji tidak layak.
- Jangan hapus thunks dari proyek Anda untuk sebagian besar kasus sederhana.
- Jangan ragu untuk mengirimkan barang
yield put(someActionThunk)
jika itu masuk akal.
Jika Anda takut menggunakan Redux-saga (atau Redux-observable) tetapi hanya perlu pola decoupling, periksa redux-dispatch-subscribe : itu memungkinkan untuk mendengarkan pengiriman dan memicu pengiriman baru di pendengar.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});