Jangan terjebak dalam pemikiran bahwa perpustakaan harus meresepkan cara melakukan segalanya . Jika Anda ingin melakukan sesuatu dengan batas waktu dalam JavaScript, Anda perlu menggunakannya setTimeout
. Tidak ada alasan mengapa tindakan Redux harus berbeda.
Redux memang menawarkan beberapa cara alternatif untuk menangani hal-hal yang tidak sinkron, tetapi Anda hanya boleh menggunakannya ketika Anda menyadari Anda mengulangi terlalu banyak kode. Kecuali Anda memiliki masalah ini, gunakan apa yang ditawarkan bahasa dan gunakan solusi paling sederhana.
Menulis Async Code Inline
Sejauh ini, ini adalah cara paling sederhana. Dan tidak ada yang spesifik untuk Redux di sini.
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Demikian pula, dari dalam komponen yang terhubung:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Satu-satunya perbedaan adalah bahwa dalam komponen yang terhubung Anda biasanya tidak memiliki akses ke toko itu sendiri, tetapi mendapatkan salah satu dispatch()
atau pembuat tindakan tertentu disuntikkan sebagai alat peraga. Namun ini tidak ada bedanya bagi kami.
Jika Anda tidak suka membuat kesalahan ketik saat mengirim tindakan yang sama dari komponen yang berbeda, Anda mungkin ingin mengekstrak pembuat tindakan alih-alih mengirimkan objek tindakan secara inline:
// actions.js
export function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
return { type: 'HIDE_NOTIFICATION' }
}
// component.js
import { showNotification, hideNotification } from '../actions'
this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
this.props.dispatch(hideNotification())
}, 5000)
Atau, jika sebelumnya Anda telah mengikatnya dengan connect()
:
this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)
Sejauh ini kami belum menggunakan middleware atau konsep canggih lainnya.
Mengekstrak Pencipta Tindakan Async
Pendekatan di atas berfungsi dengan baik dalam kasus-kasus sederhana tetapi Anda mungkin menemukan bahwa ia memiliki beberapa masalah:
- Ini memaksa Anda untuk menduplikasi logika ini di mana pun Anda ingin menampilkan pemberitahuan.
- Notifikasi tidak memiliki ID sehingga Anda akan memiliki kondisi balapan jika Anda menampilkan dua notifikasi dengan cukup cepat. Ketika batas waktu pertama selesai, itu akan mengirimkan
HIDE_NOTIFICATION
, menyembunyikan pemberitahuan kedua lebih cepat daripada setelah batas waktu.
Untuk mengatasi masalah ini, Anda perlu mengekstrak fungsi yang memusatkan logika batas waktu dan mengirimkan dua tindakan tersebut. Mungkin terlihat seperti ini:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
// Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
// for the notification that is not currently visible.
// Alternatively, we could store the timeout ID and call
// clearTimeout(), but we’d still want to do it in a single place.
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
Sekarang komponen dapat digunakan showNotificationWithTimeout
tanpa menduplikasi logika ini atau memiliki kondisi balapan dengan pemberitahuan berbeda:
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
Mengapa showNotificationWithTimeout()
menerima dispatch
sebagai argumen pertama? Karena itu perlu mengirimkan tindakan ke toko. Biasanya komponen memiliki akses ke dispatch
tetapi karena kita ingin fungsi eksternal untuk mengambil kendali atas pengiriman, kita perlu memberikannya kontrol atas pengiriman.
Jika Anda memiliki toko tunggal yang diekspor dari beberapa modul, Anda bisa mengimpornya dan dispatch
langsung menggunakannya:
// store.js
export default createStore(reducer)
// actions.js
import store from './store'
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
const id = nextNotificationId++
store.dispatch(showNotification(id, text))
setTimeout(() => {
store.dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout('You just logged in.')
// otherComponent.js
showNotificationWithTimeout('You just logged out.')
Ini terlihat lebih sederhana tetapi kami tidak merekomendasikan pendekatan ini . Alasan utama kami tidak suka itu adalah karena itu memaksa toko untuk menjadi lajang . Ini membuatnya sangat sulit untuk mengimplementasikan rendering server . Di server, Anda ingin setiap permintaan memiliki toko sendiri, sehingga pengguna yang berbeda mendapatkan data yang dimuat sebelumnya berbeda.
Toko tunggal juga membuat pengujian lebih sulit. Anda tidak lagi dapat mengejek toko ketika menguji pembuat tindakan karena mereka merujuk toko nyata tertentu yang diekspor dari modul tertentu. Anda bahkan tidak dapat mengatur ulang keadaannya dari luar.
Jadi, sementara Anda secara teknis dapat mengekspor toko tunggal dari modul, kami tidak mendukungnya. Jangan lakukan ini kecuali Anda yakin bahwa aplikasi Anda tidak akan pernah menambahkan rendering server.
Kembali ke versi sebelumnya:
// actions.js
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
Ini menyelesaikan masalah dengan duplikasi logika dan menyelamatkan kita dari kondisi balapan.
Thlk Middleware
Untuk aplikasi sederhana, pendekatannya sudah cukup. Jangan khawatir tentang middleware jika Anda senang.
Namun, dalam aplikasi yang lebih besar, Anda mungkin menemukan ketidaknyamanan tertentu di sekitarnya.
Sebagai contoh, sangat disayangkan kita harus berpapasan dispatch
. Ini membuatnya lebih sulit untuk memisahkan wadah dan komponen presentasi karena setiap komponen yang mengirim tindakan Redux secara tidak sinkron dengan cara di atas harus diterima dispatch
sebagai penyangga agar dapat meneruskannya lebih lanjut. Anda tidak dapat lagi mengikat pembuat tindakan dengan connect()
karena showNotificationWithTimeout()
tidak benar-benar pembuat tindakan. Itu tidak mengembalikan tindakan Redux.
Selain itu, mungkin aneh untuk mengingat fungsi mana yang merupakan pembuat tindakan sinkron seperti showNotification()
dan yang merupakan pembantu asinkron showNotificationWithTimeout()
. Anda harus menggunakannya secara berbeda dan berhati-hati agar tidak salah mengartikannya.
Ini adalah motivasi untuk menemukan cara untuk "melegitimasi" pola penyediaan dispatch
fungsi pembantu ini, dan membantu Redux "melihat" seperti pembuat tindakan sinkron sebagai kasus khusus pembuat tindakan normal daripada fungsi yang sama sekali berbeda.
Jika Anda masih bersama kami dan Anda juga mengenali sebagai masalah di aplikasi Anda, Anda dipersilakan untuk menggunakan middleware Redux Thunk .
Dalam intinya, Redux Thunk mengajarkan Redux untuk mengenali jenis tindakan khusus yang sebenarnya berfungsi:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })
// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
// ... which themselves may dispatch many times
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
setTimeout(() => {
// ... even asynchronously!
dispatch({ type: 'DECREMENT' })
}, 1000)
})
Ketika middleware ini diaktifkan, jika Anda mengirim suatu fungsi , middleware Redux Thunk akan memberikannya dispatch
sebagai argumen. Ini juga akan "menelan" tindakan seperti itu jadi jangan khawatir tentang reduksi Anda menerima argumen fungsi aneh. Pereduksi Anda hanya akan menerima tindakan objek biasa - baik dipancarkan secara langsung, atau dipancarkan oleh fungsi seperti yang baru saja kami jelaskan.
Ini tidak terlihat sangat berguna, bukan? Tidak dalam situasi khusus ini. Namun itu memungkinkan kami mendeklarasikan showNotificationWithTimeout()
sebagai pembuat tindakan Redux biasa:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
Perhatikan bagaimana fungsinya hampir identik dengan yang kita tulis di bagian sebelumnya. Namun itu tidak diterima dispatch
sebagai argumen pertama. Alih-alih mengembalikan fungsi yang menerima dispatch
sebagai argumen pertama.
Bagaimana kita menggunakannya dalam komponen kita? Jelas, kita bisa menulis ini:
// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
Kami memanggil pembuat tindakan async untuk mendapatkan fungsi batin yang hanya ingin dispatch
, dan kemudian kami lulus dispatch
.
Namun ini bahkan lebih aneh daripada versi aslinya! Mengapa kita bahkan pergi ke sana?
Karena apa yang saya katakan sebelumnya. Jika Redux Thunk middleware diaktifkan, setiap kali Anda mencoba mengirim fungsi alih-alih objek tindakan, middleware akan memanggil fungsi itu dengan dispatch
metode itu sendiri sebagai argumen pertama .
Jadi kita bisa melakukan ini sebagai gantinya:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
Akhirnya, mengirimkan tindakan asinkron (sebenarnya, serangkaian tindakan) terlihat tidak berbeda dengan mengirimkan satu tindakan secara sinkron ke komponen. Yang bagus karena komponen tidak boleh peduli apakah sesuatu terjadi secara sinkron atau asinkron. Kami baru saja mengabstraksikan hal itu.
Perhatikan bahwa karena kita “diajarkan” Redux untuk mengenali pencipta tindakan “khusus” seperti (kami menyebutnya dunk pencipta tindakan), kita sekarang dapat menggunakannya di mana saja di mana kita akan menggunakan pencipta tindakan biasa. Sebagai contoh, kita dapat menggunakannya dengan connect()
:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
// component.js
import { connect } from 'react-redux'
// ...
this.props.showNotificationWithTimeout('You just logged in.')
// ...
export default connect(
mapStateToProps,
{ showNotificationWithTimeout }
)(MyComponent)
Status Membaca dalam Thunks
Biasanya reduksi Anda mengandung logika bisnis untuk menentukan status berikutnya. Namun, reduksi hanya masuk setelah tindakan dikirim. Bagaimana jika Anda memiliki efek samping (seperti memanggil API) di pembuat tindakan thunk, dan Anda ingin mencegahnya dalam kondisi tertentu?
Tanpa menggunakan middleware thunk, Anda cukup melakukan pemeriksaan di dalam komponen:
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
Namun, tujuan mengekstraksi pembuat tindakan adalah memusatkan logika berulang ini di banyak komponen. Untungnya, Redux Thunk menawarkan Anda cara untuk membaca keadaan toko Redux saat ini. Selain itu dispatch
, getState
argumen ini juga berfungsi sebagai argumen kedua untuk fungsi yang Anda kembali dari pembuat tindakan thunk Anda. Ini memungkinkan thunk membaca keadaan toko saat ini.
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch, getState) {
// Unlike in a regular action creator, we can exit early in a thunk
// Redux doesn’t care about its return value (or lack of it)
if (!getState().areNotificationsEnabled) {
return
}
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
Jangan menyalahgunakan pola ini. Ini bagus untuk membatalkan panggilan API ketika ada data yang di-cache tersedia, tetapi itu bukan fondasi yang sangat baik untuk membangun logika bisnis Anda. Jika Anda getState()
hanya menggunakan untuk mengirim tindakan yang berbeda secara kondisional, pertimbangkan untuk memasukkan logika bisnis ke dalam reduksi.
Langkah selanjutnya
Sekarang setelah Anda memiliki intuisi dasar tentang cara kerja thunks, lihat contoh Redux async yang menggunakannya.
Anda dapat menemukan banyak contoh di mana thunks mengembalikan Janji. Ini tidak diperlukan tetapi bisa sangat nyaman. Redux tidak peduli apa yang Anda kembali dari thunk, tetapi itu memberi Anda nilai pengembaliannya dispatch()
. Inilah sebabnya mengapa Anda dapat mengembalikan Janji dari seorang pencuri dan menunggu sampai selesai dengan menelepon dispatch(someThunkReturningPromise()).then(...)
.
Anda juga dapat membagi pembuat tindakan thunk yang kompleks menjadi beberapa pembuat aksi thunk yang lebih kecil. The dispatch
Metode yang disediakan oleh thunks dapat menerima thunks sendiri, sehingga Anda dapat menerapkan pola rekursif. Sekali lagi, ini bekerja paling baik dengan Janji karena Anda dapat menerapkan aliran kontrol asinkron di atas semua itu.
Untuk beberapa aplikasi, Anda mungkin menemukan diri Anda dalam situasi di mana persyaratan aliran kontrol asinkron Anda terlalu rumit untuk diungkapkan dengan thunks. Misalnya, mencoba ulang permintaan yang gagal, aliran otorisasi ulang dengan token, atau onboarding langkah-demi-langkah bisa terlalu bertele-tele dan rawan kesalahan saat ditulis dengan cara ini. Dalam hal ini, Anda mungkin ingin melihat solusi aliran kontrol asinkron yang lebih canggih seperti Redux Saga atau Redux Loop . Evaluasi mereka, bandingkan contoh yang relevan dengan kebutuhan Anda, dan pilih yang paling Anda sukai.
Akhirnya, jangan gunakan apa pun (termasuk thunks) jika Anda tidak benar-benar membutuhkannya. Ingat bahwa, tergantung pada persyaratan, solusi Anda mungkin terlihat sesederhana itu
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Jangan berkeringat kecuali Anda tahu mengapa Anda melakukan ini.
redux-saga
jawaban berdasarkan saya jika Anda menginginkan sesuatu yang lebih baik daripada thunks. Terlambat menjawab sehingga Anda harus menggulir waktu yang lama sebelum melihatnya :) tidak berarti itu tidak layak dibaca. Berikut ini jalan pintas: stackoverflow.com/a/38574266/82609