Yapmazsın.
Ama ... redux-saga kullanmalısın :)
Dan Abramov'un cevabı doğru redux-thunk
ama redux-saga hakkında biraz daha konuşacağım benzer ama daha güçlü .
Zorunlu VS beyanı
- DOM : jQuery zorunlu / Tepki bildiriyor
- monads : IO zorunlu / Ücretsiz beyan edici
- Redux etkileri :
redux-thunk
zorunludur / redux-saga
beyan edicidir
Bir IO monad veya bir söz gibi ellerinizde bir thunk olduğunda, yürüttükten sonra ne yapacağını kolayca bilemezsiniz. Bir gövdeyi test etmenin tek yolu, onu çalıştırmak ve dağıtıcıyı (veya daha fazla şeyle etkileşime girerse tüm dış dünyayı) alay etmektir.
Alaycı kullanıyorsanız, fonksiyonel programlama yapmıyorsunuzdur.
Yan etkiler merceğinden görülen alaylar, kodunuzun saf olmadığı bir işarettir ve fonksiyonel programcının gözünde bir şeyin yanlış olduğunu kanıtlar. Buzdağının sağlam olduğunu kontrol etmemize yardımcı olacak bir kütüphane indirmek yerine, etrafına yelken açmalıyız. Sert bir TDD / Java adamı bana bir keresinde Clojure'da alay etme şeklini sordu. Cevap, genellikle bilmiyoruz. Genellikle kodumuzu yeniden düzenlememiz gereken bir işaret olarak görürüz.
Kaynak
Sagalar (uygulandıkları gibi) redux-saga
) bildirimlidir ve Free monad veya React bileşenleri gibi, herhangi bir alay olmadan test etmek çok daha kolaydır.
Ayrıca bu makaleye bakın :
modern FP'de, programlar yazmamalıyız - programların tanımlarını yazmalıyız.
(Aslında, Redux-saga bir melez gibidir: akış zorunludur, ancak etkileri açıklayıcıdır)
Karışıklık: eylemler / olaylar / komutlar ...
Ön uç dünyasında, CQRS / EventSourcing ve Flux / Redux gibi bazı arka uç kavramlarının nasıl ilişkili olabileceği konusunda çok fazla karışıklık var, çünkü çoğunlukla Flux'ta bazen hem zorunlu kodu ( LOAD_USER
) hem de olayları () ve olayları (USER_LOADED
). Olay satın alma gibi, yalnızca olayları göndermeniz gerektiğine inanıyorum.
Pratikte destan kullanma
Kullanıcı profiline bağlantı içeren bir uygulama düşünün. Bunu her ara katman yazılımı ile ele almanın deyimsel yolu:
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 })
}
}
Bu destan şu anlama gelir:
bir kullanıcı adı her tıklandığında, kullanıcı profilini getirir ve yüklenen profille bir olay gönderir.
Gördüğünüz gibi, bazı avantajları var redux-saga
.
takeLatest
Yalnızca son kullanıcı adının verilerini tıklatmakla ilgilendiğinizi ifade etmek için izinlerin kullanılması ( kullanıcının çok sayıda kullanıcı adına çok hızlı tıklaması durumunda eşzamanlılık sorunlarını ele alın). Bu tür şeyler thunks ile zordur. KullanabilirdintakeEvery
bu davranışı istemiyorsanız.
Aksiyon oluşturucuları saf tutarsınız. ActionCreators'ı (sagas put
ve bileşenlerde dispatch
) tutmanın hala yararlı olduğunu unutmayın, çünkü ileride eylem doğrulaması (onaylar / akış / daktilo) eklemenize yardımcı olabilir.
Efektler açıklandığı için kodunuz çok daha test edilebilir hale geliyor
Artık rpc benzeri çağrıları tetiklemek zorunda değilsiniz actions.loadUser()
. Kullanıcı arayüzünüzün OLAN şeyleri göndermesi yeterlidir. Artık olayları değil (her zaman geçmiş zamanda!) Ateş ediyoruz . Bu, ayrıştırılmış "ördekler" veya Sınırlı Bağlamlar oluşturabileceğiniz ve destanın bu modüler bileşenler arasındaki bağlantı noktası olarak işlev görebileceği anlamına gelir .
Bu, görünümlerinizin yönetilmesi daha kolay olduğu anlamına gelir çünkü artık olanlar ile sonuç olarak ne olması gerektiği arasında bir çeviri katmanı içermelerine gerek yoktur.
Örneğin sonsuz bir kaydırma görünümü hayal edin. CONTAINER_SCROLLED
yol açabilir NEXT_PAGE_LOADED
, ama gerçekten biz başka bir sayfa yüklemek gerekip gerekmediğini karar vermek kaydırılabilir konteyner sorumluluğundadır? Daha sonra, son sayfanın başarılı bir şekilde yüklenip yüklenmediği veya yüklenmeye çalışan bir sayfa olup olmadığı veya yüklenecek başka bir öğe olup olmadığı gibi daha karmaşık şeylerin farkında olmalı? Ben öyle düşünmüyorum: maksimum yeniden kullanılabilirlik için kaydırılabilir kap sadece kaydırıldığını açıklamalıdır. Bir sayfanın yüklenmesi o kaydırmanın "iş etkisi" dir
Bazıları, jeneratörlerin doğası gereği yerel değişkenlerle redux mağazasının dışındaki durumu gizleyebileceğini iddia edebilir, ancak zamanlayıcılar vb. Ve select
şimdi Redux mağazanızdan bir durum almanıza izin veren bir efekt var.
Sagas zaman yolculuğu yapabilir ve aynı zamanda üzerinde çalışmakta olan karmaşık akış günlüğü ve geliştirme araçlarına da olanak tanır. Zaten uygulanmış olan bazı basit zaman uyumsuz akış günlüğü:
Ayrışma
Sagalar sadece redux gövdelerinin yerini almaz. Arka uç / dağıtılmış sistemlerden / olay kaynaklarından gelirler.
Sagas'ın redux gövdelerinizi daha iyi test edilebilirlikle değiştirmek için burada olduğu çok yaygın bir yanlış anlamadır. Aslında bu sadece redux-saga'nın bir uygulama detayı. Beyan edilebilir etkiler kullanmak test edilebilirlik için gövdeden daha iyidir, ancak destan modeli zorunlu veya bildirici kodun üstüne uygulanabilir.
İlk olarak, destan, uzun süren işlemleri (nihai tutarlılık) ve farklı sınırlı bağlamlardaki işlemleri (etki alanına dayalı tasarım jargonu) koordine etmeyi sağlayan bir yazılım parçasıdır.
Bunu ön uç dünyası için basitleştirmek için widget1 ve widget2 olduğunu hayal edin. Widget1 üzerindeki bazı düğmeler tıklatıldığında, widget2 üzerinde bir etkisi olmalıdır. 2 widget'ı birbirine bağlamak yerine (yani widget1, widget2'yi hedefleyen bir eylem gönderir), widget1 yalnızca düğmesinin tıklatıldığını gönderir. Ardından destan bu düğmeyi dinleyin ve widget2'nin farkında olduğu yeni bir olay göndererek widget2'yi güncelleyin.
Bu, basit uygulamalar için gerekli olmayan bir dolaylı yükleme düzeyi ekler, ancak karmaşık uygulamaları ölçeklendirmeyi daha kolay hale getirir. Artık widget1 ve widget2'yi farklı npm depolarına yayınlayabilirsiniz, böylece genel eylem kayıtlarını paylaşmalarına gerek kalmadan birbirlerini asla bilmek zorunda kalmazlar. 2 widget artık ayrı yaşayabilen sınırlı bağlamlardır. Birbirlerinin tutarlı olması gerekmez ve diğer uygulamalarda da tekrar kullanılabilirler. Destan, iki widget arasındaki bunları, işletmeniz için anlamlı bir şekilde koordine eden bağlantı noktasıdır.
Redux uygulamanızı nasıl yapılandıracağınızla ilgili, Redux-saga'yı ayırma nedenleriyle kullanabileceğiniz bazı güzel makaleler:
Somut bir kullanıcı tabanı: bildirim sistemi
Bileşenlerimin uygulama içi bildirimlerin görüntülenmesini tetikleyebilmesini istiyorum. Ancak, bileşenlerimin kendi iş kurallarına sahip bildirim sistemine (aynı anda görüntülenen maksimum 3 bildirim, bildirim kuyruğu, 4 saniye görüntüleme süresi vb.) Yüksek düzeyde birleşmesini istemiyorum.
JSX bileşenlerimin bir bildirimin ne zaman gösterileceğini / gizleneceğine karar vermesini istemiyorum. Sadece bir bildirim isteme ve karmaşık kuralları destanın içinde bırakma yeteneği veriyorum. Bu tür şeylerin thunks veya vaatlerle uygulanması oldukça zordur.
Burada bunun destan ile nasıl yapılabileceğini anlattım
Neden destan deniyor?
Destan terimi arka uç dünyadan geliyor. Başlangıçta Yassine'yi (Redux-saga'nın yazarı) uzun bir tartışmada tanıttım .
Başlangıçta, bu terim bir kâğıt ile tanıtıldı , destan modelinin dağıtılmış işlemlerde nihai tutarlılığı işlemek için kullanılması gerekiyordu, ancak kullanımı artık arka plan geliştiricileri tarafından daha geniş bir tanımlamaya genişletildi, böylece artık "süreç yöneticisi" (bir şekilde orijinal destan modeli, işlem yöneticisinin özel bir şeklidir).
Bugün "destan" terimi 2 farklı şeyi tanımlayabileceğinden kafa karıştırıcı. Redux-saga'da kullanıldığından, dağıtılmış işlemleri ele almanın bir yolunu değil, uygulamanızdaki eylemleri koordine etmenin bir yolunu tanımlar. redux-saga
de denilebilirdi redux-process-manager
.
Ayrıca bakınız:
Alternatifler
Jeneratör kullanma fikrinden hoşlanmıyorsanız ancak destan deseni ve ayırma özellikleri ile ilgileniyorsanız, aynısını aynı modeli tanımlamak için adı kullanan redux-gözlemlenebilirepic
, ancak RxJS ile de elde edebilirsiniz. Rx'i zaten biliyorsanız, kendinizi evinizde gibi hissedeceksiniz.
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
}))
);
Bazı redux-saga yararlı kaynakları
2017 tavsiyeleri
- Redux-saga'yı sadece kullanmak uğruna aşırı kullanmayın. Yalnızca test edilebilir API çağrıları buna değmez.
- En basit durumlar için gövdelerinizi projenizden çıkarmayın.
yield put(someActionThunk)
Mantıklıysa, gövdelerinizi göndermekten çekinmeyin .
Redux-saga (veya Redux-gözlemlenebilir) kullanmaktan korkuyorsanız, ancak ayırma düzenine ihtiyacınız varsa, redux-dispatch-abone olup olmadığını kontrol edin : gönderileri dinlemeye ve dinleyicide yeni gönderileri tetiklemeye izin verir.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});