Bir kütüphanenin her şeyi nasıl yapacağını reçete etmesi gerektiğini düşünme tuzağına düşmeyin . JavaScript'te zaman aşımı olan bir şey yapmak istiyorsanız, kullanmanız gerekir setTimeout
. Redux eylemlerinin farklı olması için hiçbir neden yoktur.
Redux , eşzamansız şeylerle başa çıkmanın bazı alternatif yollarını sunar, ancak bunları yalnızca çok fazla kod yinelediğinizi fark ettiğinizde kullanmalısınız. Bu sorun yoksa, dilin sunduklarını kullanın ve en basit çözümü bulun.
Asenkron Kod Satır İçi Yazma
Bu en basit yol. Ve burada Redux'a özgü bir şey yok.
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Benzer şekilde, bağlı bir bileşenin içinden:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Tek fark, bağlı bir bileşende genellikle mağazanın kendisine erişiminizin olmaması, ancak dispatch()
sahne olarak ya da belirli eylem yaratıcılarının enjekte edilmesidir. Ancak bu bizim için bir fark yaratmıyor.
Aynı eylemleri farklı bileşenlerden gönderirken yazım hatası yapmaktan hoşlanmıyorsanız, eylem nesnelerini satır içinde göndermek yerine eylem oluşturucuları çıkarmak isteyebilirsiniz:
// 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)
Veya daha önce bunlarla bağladıysanız connect()
:
this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)
Şimdiye kadar herhangi bir ara katman yazılımı veya başka bir gelişmiş kavram kullanmadık.
Async Action Creator ayıklanıyor
Yukarıdaki yaklaşım basit durumlarda iyi çalışır, ancak birkaç problemi olduğunu görebilirsiniz:
- Bu mantığı, bildirim göstermek istediğiniz herhangi bir yere kopyalamanıza zorlar.
- Bildirimlerin kimliği yoktur, bu nedenle iki bildirimi yeterince hızlı gösterirseniz bir yarış durumunuz olur. İlk zaman aşımı bittiğinde,
HIDE_NOTIFICATION
ikinci bildirimi zaman aşımından sonra yanlışlıkla yanlışlıkla gizleyerek gönderilir .
Bu sorunları çözmek için, zaman aşımı mantığını merkezileştiren ve bu iki eylemi gönderen bir işlev çıkarmanız gerekir. Şöyle görünebilir:
// 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)
}
Artık bileşenler showNotificationWithTimeout
bu mantığı çoğaltmadan veya farklı bildirimlere sahip yarış koşullarına sahip olmadan kullanabilirler :
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
Neden ilk argüman olarak showNotificationWithTimeout()
kabul dispatch
ediliyor? Çünkü mağazaya eylemler göndermesi gerekiyor. Normalde bir bileşenin erişimi vardır, dispatch
ancak harici bir işlevin dağıtım üzerinde kontrol sahibi olmasını istediğimizden, dağıtım üzerinde kontrol sahibi olmamız gerekir.
Bazı modüllerden dışa aktarılan tekli bir mağazanız varsa, onu içe ve dispatch
doğrudan üzerine aktarabilirsiniz :
// 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.')
Bu daha basit görünüyor ancak bu yaklaşımı önermiyoruz . Sevmememizin temel nedeni, mağazayı tekil olmaya zorlamasıdır . Bu, sunucu oluşturma işlemini çok zorlaştırır . Sunucuda, her kullanıcının kendi deposuna sahip olmasını istersiniz, böylece farklı kullanıcılar önceden yüklenmiş farklı veriler elde eder.
Tek bir mağaza da testi zorlaştırır. Belirli bir modülden dışa aktarılan belirli bir gerçek mağazaya başvurduklarından, eylem oluşturucularını test ederken artık bir mağazayla alay edemezsiniz. Durumunu dışarıdan bile sıfırlayamazsınız.
Tek bir mağazayı bir modülden teknik olarak dışa aktarabilirken, cesaretini kırıyoruz. Uygulamanızın asla sunucu oluşturma eklemeyeceğinden emin değilseniz bunu yapmayın.
Önceki sürüme dönme:
// 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.')
Bu, mantığın tekrarlanmasındaki sorunları çözer ve bizi yarış koşullarından kurtarır.
Thunk Middleware
Basit uygulamalar için yaklaşım yeterli olmalıdır. Eğer memnun iseniz ara katman yazılımı hakkında endişelenmeyin.
Ancak daha büyük uygulamalarda, çevresinde bazı zorluklar bulabilirsiniz.
Örneğin, dispatch
etrafta dolaşmamız talihsiz görünüyor . Bu, kapsayıcı ve sunum bileşenlerini ayırmayı zorlaştırır, çünkü Redux eylemlerini yukarıdaki şekilde eşzamansız olarak gönderen herhangi bir bileşen, dispatch
daha fazla geçebilmesi için bir prop olarak kabul etmelidir . Artık bir eylem oluşturucu ile bağlantı connect()
kuramazsınız çünkü showNotificationWithTimeout()
gerçekte bir eylem oluşturucu değildir. Redux eylemi döndürmez.
Ayrıca, hangi işlevlerin eşzamanlı eylem oluşturucuları showNotification()
ve hangilerinin eşzamansız yardımcılar olduğunu hatırlamak garip olabilir showNotificationWithTimeout()
. Onları farklı kullanmalı ve birbirleriyle karıştırmamaya dikkat etmelisiniz.
Bu, yardımcı bir işlev sağlama şeklini "meşrulaştırmanın" bir yolunu bulmak dispatch
ve Redux'un tamamen farklı işlevler yerine normal eylem yaratıcılarının özel bir durumu olarak "eşzamansız" içerik oluşturucuları "görmesine" yardımcı olma motivasyonuydu .
Hala bizimleyseniz ve uygulamanızda bir sorun olarak da tanıyorsanız, Redux Thunk ara katman yazılımını kullanabilirsiniz.
Bir özette, Redux Thunk Redux'a aslında fonksiyon olan özel eylem türlerini tanımasını öğretir:
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)
})
Bu ara katman yazılımı etkinleştirildiğinde, bir işlev gönderirseniz , Redux Thunk ara katmanı işlevidispatch
bağımsız değişken olarak verir . Ayrıca bu tür eylemleri “yutacaktır”, bu nedenle redüktörlerinizin garip işlev argümanları almasından endişe etmeyin. Düşürücülerinize yalnızca doğrudan yayılan veya az önce açıkladığımız işlevler tarafından yayılan düz nesne eylemleri gönderilir.
Bu çok kullanışlı görünmüyor, değil mi? Bu özel durumda değil. Bununla birlikte showNotificationWithTimeout()
, normal bir Redux eylem yaratıcısı olarak beyan etmemizi sağlar :
// 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)
}
}
İşlevin, önceki bölümde yazdığımız işlevle neredeyse aynı olduğunu unutmayın. Ancak dispatch
ilk argüman olarak kabul edilmez . Bunun yerine , ilk argüman olarak kabul eden bir işlev döndürürdispatch
.
Bileşenimizde nasıl kullanırız? Kesinlikle, bunu yazabiliriz:
// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
Async eylem yaratıcısını, sadece isteyen iç işlevi almaya çağırıyoruz dispatch
ve sonra geçiyoruz dispatch
.
Ancak bu orijinal versiyondan daha garip! Neden böyle gittik?
Sana daha önce söylediğim yüzünden. Redux Thunk ara katman yazılımı etkinse, eylem nesnesi yerine bir işlev göndermeye çalıştığınızda, ara katman yazılımı bu işlevi dispatch
yöntemle birlikte ilk bağımsız değişken olarak çağırır .
Bunun yerine bunu yapabiliriz:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
Son olarak, eşzamansız bir eylemi (gerçekten bir dizi eylem) göndermek, bileşene eşzamanlı olarak tek bir eylem göndermekten farklı değildir. Bu iyidir çünkü bileşenler bir şeyin eşzamanlı veya eşzamansız olup olmadığını umursamamalıdır. Sadece soyutladık.
Redux'a bu tür “özel” aksiyon içerik oluşturucularını tanımayı “öğrettiğimiz” için (bunlara thunk aksiyon içerik oluşturucuları diyoruz ) artık bunları normal aksiyon içerik oluşturucularını kullanacağımız herhangi bir yerde kullanabileceğimizi unutmayın. Örneğin, bunları aşağıdakilerle birlikte kullanabiliriz 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)
Thunks'ta Okuma Durumu
Redüktörleriniz genellikle bir sonraki durumu belirlemek için iş mantığını içerir. Ancak, redüktörler ancak eylemler gönderildikten sonra devreye girer. Thunk eylem oluşturucuda bir yan etkiniz (API çağırmak gibi) varsa ve bir koşulda bunu önlemek istiyorsanız ne olur?
Thunk ara katman yazılımı kullanmadan, bu kontrolü bileşen içinde yapmanız yeterlidir:
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
Bununla birlikte, bir eylem oluşturucunun çıkarılmasının amacı, bu tekrarlayan mantığı birçok bileşende merkezileştirmekti. Neyse ki, Redux Thunk size Redux mağazasının mevcut durumunu okumak için bir yol sunuyor . Buna ek olarak dispatch
, getState
thunk eylem oluşturucunuzdan döndürdüğünüz işleve ikinci argüman olarak da geçer . Bu, thunk'ın mağazanın mevcut durumunu okumasını sağlar.
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)
}
}
Bu kalıbı kötüye kullanmayın. Önbelleğe alınmış veriler olduğunda API çağrılarını kurtarmak için iyidir, ancak iş mantığınızı oluşturmak için çok iyi bir temel değildir. getState()
Yalnızca koşullu olarak farklı eylemler göndermek için kullanıyorsanız , bunun yerine iş mantığını indirgeyicilere koymayı düşünün.
Sonraki adımlar
Artık thunks'ın nasıl çalıştığı hakkında temel bir sezginiz olduğuna göre , bunları kullanan Redux async örneğine bakın .
Thunks'ın Promises verdiği birçok örnek bulabilirsiniz. Bu gerekli değildir, ancak çok uygun olabilir. Redux bir thunk'tan ne döndürdüğünüzü umursamaz, ancak size dönüş değerini verir dispatch()
. Bu yüzden bir sözden bir söz verebilir ve arayarak tamamlanmasını bekleyebilirsiniz dispatch(someThunkReturningPromise()).then(...)
.
Ayrıca karmaşık thunk eylem yaratıcılarını daha küçük thunk eylem yaratıcılarına bölebilirsiniz. dispatch
Özyinelemeli model uygulamak, böylece thunks tarafından sağlanan yöntem, kendisi thunks kabul edebilir. Yine, bu en iyi Promises ile çalışır çünkü bunun üzerine asenkron kontrol akışı uygulayabilirsiniz.
Bazı uygulamalar için, kendinizi eşzamansız kontrol akış gereksinimlerinizin gövdelerle ifade edilemeyecek kadar karmaşık olduğu bir durumda bulabilirsiniz. Örneğin, başarısız istekleri yeniden denemek, jetonlarla yeniden yetkilendirme akışı veya adım adım alıştırma bu şekilde yazıldığında çok ayrıntılı ve hataya açık olabilir. Bu durumda, Redux Saga veya Redux Loop gibi daha gelişmiş asenkron kontrol akış çözümlerine bakmak isteyebilirsiniz . Bunları değerlendirin, ihtiyaçlarınızla ilgili örnekleri karşılaştırın ve en çok beğendiğinizi seçin.
Son olarak, gerçek bir gereksiniminiz yoksa hiçbir şey (thunks dahil) kullanmayın. Gereksinimlere bağlı olarak, çözümünüzün
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Bunu neden yaptığınızı bilmiyorsanız terlemeyin.
redux-saga
Thunks'tan daha iyi bir şey istiyorsanız, temel cevabımı kontrol etmeyi unutmayın . Geç cevap bu yüzden görmeden önce uzun bir süre kaydırmak zorunda :) okumaya değmez anlamına gelmez. İşte bir kısayol: stackoverflow.com/a/38574266/82609