Zaman aşımı ile bir Redux eylemi nasıl gönderilir?


891

Uygulamamın bildirim durumunu güncelleyen bir işlemim var. Genellikle, bu bildirim bir tür hata veya bilgi olacaktır. Sonra 5 saniye sonra başka bir eylem göndermek gerekir bildirimi başlangıç ​​durumuna dönecek, böylece hiçbir bildirim. Bunun temel nedeni, bildirimlerin 5 saniye sonra otomatik olarak kaybolması için işlevsellik sağlamaktır.

Kullanmak setTimeoutve başka bir eylem döndürme konusunda hiçbir şansım yoktu ve bunun çevrimiçi nasıl yapıldığını bulamıyorum. Yani herhangi bir tavsiye açığız.


30
redux-sagaThunks'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
Sebastien Lorber


2
redux-saga harika ama jeneratör işlevlerinden gelen yanıtları desteklemiyor gibi görünüyor. Tepki içeren daktilo yazıyor olmanız önemli olabilir.
Crhistian Ramirez

Yanıtlar:


2617

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_NOTIFICATIONikinci 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 showNotificationWithTimeoutbu 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 dispatchediliyor? Çünkü mağazaya eylemler göndermesi gerekiyor. Normalde bir bileşenin erişimi vardır, dispatchancak 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 dispatchdoğ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, dispatchetrafta 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, dispatchdaha 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 dispatchve 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 dispatchilk 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 dispatchve 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 dispatchyö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, getStatethunk 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.


27
Asenkron eylemler, ortak bir soruna bu kadar basit ve zarif bir çözüm gibi görünüyor. Neden ara katman yazılımı gerekmeden redux için desteklenmiyor? Bu cevap o zaman çok daha özlü olabilir.
Phil Mander

83
@PhilMander Çünkü github.com/raisemarketplace/redux-loop veya github.com/yelouafi/redux-saga gibi (daha fazla değilse) zarif birçok alternatif desen var . Redux düşük seviyeli bir araçtır. Beğendiğiniz bir üst kümeyi oluşturabilir ve ayrı olarak dağıtabilirsiniz.
Dan Abramov

16
Bunu açıklayabilir misiniz: * iş mantığını indirgeyicilere koymayı düşünün *, bu bir eylemi göndermem gerektiği anlamına gelir ve daha sonra indirgeyicime eyaletime bağlı olarak başka hangi eylemleri göndereceğimi belirler mi? Benim sorum, daha sonra diğer eylemleri doğrudan redüktöre gönderebilir miyim ve eğer değilse, onları nereden gönderebilirim?
froginvasion

25
Bu cümle yalnızca senkron dava için geçerlidir. Örneğin, if (cond) dispatch({ type: 'A' }) else dispatch({ type: 'B' })belki yazarsanız , geçerli duruma ve dispatch({ type: 'C', something: cond })bunun yerine bağlı olarak eylemi redüktörlerde yok saymayı seçmelisiniz action.something.
Dan Abramov

29
@DanAbramov Sadece bu konuda benim oyumu aldınız "Eğer bu probleminiz yoksa, dilin sunduklarını kullanın ve en basit çözümü tercih edin." Ancak bundan sonra kimin yazdığını anladım!
Matt Lacey

189

Redux-saga kullanma

Dan Abramov'un dediği gibi, eşzamansız kodunuz üzerinde daha gelişmiş kontrol istiyorsanız, redux-saga'ya bakabilirsiniz .

Bu cevap basit bir örnektir, redux-saga'nın uygulamanız için neden yararlı olabileceğine dair daha iyi açıklamalar istiyorsanız, bu diğer yanıtı kontrol edin .

Genel fikir, Redux-saga'nın senkronize kod gibi görünen async kodunu kolayca yazmanıza izin veren bir ES6 jeneratör yorumlayıcısı sunmasıdır (bu nedenle Redux-saga'da döngüler genellikle sonsuz bulabilirsiniz). Her nasılsa, Redux-saga kendi dilini doğrudan Javascript içinde oluşturuyor. Redux-saga'nın ilk başta öğrenmesi biraz zor olabilir, çünkü jeneratörleri temel olarak anlamanız gerekir, ancak Redux-saga tarafından sunulan dili de anlarsınız.

Burada redux-saga üzerine inşa ettiğim bildirim sistemini anlatmaya çalışacağım. Bu örnek şu anda üretimde çalışmaktadır.

Gelişmiş bildirim sistemi özellikleri

  • Bir bildirimin görüntülenmesini isteyebilirsiniz
  • Gizlemek için bir bildirim isteyebilirsiniz
  • Bir bildirim 4 saniyeden fazla gösterilmemelidir
  • Aynı anda birden fazla bildirim görüntülenebilir
  • Aynı anda en fazla 3 bildirim görüntülenemez
  • Görüntülenen 3 bildirim varken bir bildirim istenirse, kuyruğa al / ertele.

Sonuç

Benim üretim uygulaması Stample.com ekran görüntüsü

tost

kod

Burada bildirime bir isim verdim toastama bu bir adlandırma detayı.

function* toastSaga() {

    // Some config constants
    const MaxToasts = 3;
    const ToastDisplayTime = 4000;


    // Local generator state: you can put this state in Redux store
    // if it's really important to you, in my case it's not really
    let pendingToasts = []; // A queue of toasts waiting to be displayed
    let activeToasts = []; // Toasts currently displayed


    // Trigger the display of a toast for 4 seconds
    function* displayToast(toast) {
        if ( activeToasts.length >= MaxToasts ) {
            throw new Error("can't display more than " + MaxToasts + " at the same time");
        }
        activeToasts = [...activeToasts,toast]; // Add to active toasts
        yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
        yield call(delay,ToastDisplayTime); // Wait 4 seconds
        yield put(events.toastHidden(toast)); // Hide the toast
        activeToasts = _.without(activeToasts,toast); // Remove from active toasts
    }

    // Everytime we receive a toast display request, we put that request in the queue
    function* toastRequestsWatcher() {
        while ( true ) {
            // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
            const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
            const newToast = event.data.toastData;
            pendingToasts = [...pendingToasts,newToast];
        }
    }


    // We try to read the queued toasts periodically and display a toast if it's a good time to do so...
    function* toastScheduler() {
        while ( true ) {
            const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
            if ( canDisplayToast ) {
                // We display the first pending toast of the queue
                const [firstToast,...remainingToasts] = pendingToasts;
                pendingToasts = remainingToasts;
                // Fork means we are creating a subprocess that will handle the display of a single toast
                yield fork(displayToast,firstToast);
                // Add little delay so that 2 concurrent toast requests aren't display at the same time
                yield call(delay,300);
            }
            else {
                yield call(delay,50);
            }
        }
    }

    // This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
    yield [
        call(toastRequestsWatcher),
        call(toastScheduler)
    ]
}

Ve redüktör:

const reducer = (state = [],event) => {
    switch (event.name) {
        case Names.TOAST_DISPLAYED:
            return [...state,event.data.toastData];
        case Names.TOAST_HIDDEN:
            return _.without(state,event.data.toastData);
        default:
            return state;
    }
};

kullanım

Sadece TOAST_DISPLAY_REQUESTEDolayları gönderebilirsiniz . 4 istek gönderirseniz, yalnızca 3 bildirim görüntülenir ve 1. bildirim kaybolduktan sonra dördüncü bildirim biraz sonra görünür.

Özellikle TOAST_DISPLAY_REQUESTEDJSX'ten göndermeyi önermediğimi unutmayın. Mevcut uygulama etkinliklerinizi dinleyen başka bir efsane eklemeyi tercih edersiniz ve ardından TOAST_DISPLAY_REQUESTEDşunları gönderirsiniz : bildirimi tetikleyen bileşeninizin bildirim sistemine sıkıca bağlanması gerekmez.

Sonuç

Kodum mükemmel değil ama aylarca 0 hata ile üretimde çalışıyor. Redux-saga ve jeneratörler başlangıçta biraz zor ama onları anladıktan sonra bu tür bir sistem kurmak oldukça kolay.

Daha karmaşık kuralları uygulamak bile oldukça kolaydır:

  • çok fazla bildirim "kuyruğa alındığında", her bir bildirim için daha az görüntüleme süresi sağlayın, böylece kuyruk boyutu daha hızlı azalabilir.
  • pencere boyutu değişikliklerini algılama ve görüntülenen maksimum bildirim sayısını buna göre değiştirme (örneğin, masaüstü = 3, telefon portresi = 2, telefon manzarası = 1)

Dürüst olmak gerekirse, bu tür şeyleri thunks ile düzgün bir şekilde uygulamakta iyi şanslar.

Redux-saga'ya çok benzeyen redux-gözlemlenebilir ile aynı şeyi yapabileceğinizi unutmayın . Neredeyse aynı ve jeneratörler ve RxJS arasında bir tat meselesi.


18
Sorunuzun daha önce sorulduğu zaman yanıtınızın daha önce gelmesini diliyorum, çünkü böyle iş mantığı için Saga yan etkiler kütüphanesini kullanmaya daha fazla katlanamıyorum. Redüktörler ve Eylem Yaratıcıları devlet geçişleri içindir. İş akışları durum geçiş işlevleri ile aynı değildir. İş akışları geçişlerden geçer, ancak geçişlerin kendileri değildir. Redux + React kendi başına eksiktir - Redux Saga'nın bu kadar yararlı olmasının nedeni budur.
Atticus

4
Teşekkürler, redux-saga'yı bu nedenlerden dolayı popüler hale getirmek için elimden geleni yapmaya çalışıyorum :) çok az insan şu anda redux-saga'nın sadece thunks için bir yedek olduğunu düşünüyor ve redux-saga'nın karmaşık ve ayrıştırılmış iş akışlarını nasıl sağladığını görmüyorum
Sebastien Lorber

1
Kesinlikle. Eylemler ve Redüktörler devlet makinesinin bir parçasıdır. Bazen, karmaşık iş akışları için, durum makinesinin doğrudan parçası olmayan durum makinesini düzenlemek için başka bir şeye ihtiyacınız olabilir!
Atticus

2
Eylemler: Geçiş durumuna yükler / olaylar. Redüktörler: Durum geçiş fonksiyonları. Bileşenler: Durumu yansıtan kullanıcı arabirimleri. Ancak önemli bir parça eksik - bir sonraki geçişin hangi geçişi yapacağını belirleyen kendi mantığı olan birçok geçiş sürecini nasıl yönetiyorsunuz? Redux Saga!
Atticus

2
@mrbrdo Cevabımı dikkatlice okursanız, bildirim zaman aşımlarının gerçekten ele alındığını fark edeceksiniz yield call(delay,timeoutValue);: aynı API değil, aynı etkiye sahiptir
Sebastien Lorber

25

Örnek projeler içeren bir depo

Şu anda dört örnek proje var:

  1. Asenkron Kod Satır İçi Yazma
  2. Async Action Creator ayıklanıyor
  3. Redux Thunk kullanın
  4. Redux Saga kullanın

Kabul edilen cevap harika.

Ancak eksik bir şey var:

  1. Çalıştırılabilir örnek proje yok, sadece bazı kod parçacıkları.
  2. Diğer alternatifler için örnek kod yok, örneğin:
    1. Redux Saga

Bu yüzden eksik şeyleri eklemek için Hello Async deposunu oluşturdum :

  1. Çalıştırılabilir projeler. Bunları değiştirmeden indirebilir ve çalıştırabilirsiniz.
  2. Daha fazla alternatif için örnek kod sağlayın:

Redux Saga

Kabul edilen cevap, Async Code Inline, Async Action Generator ve Redux Thunk için örnek kod snippet'lerini zaten sağlıyor. Tamlık uğruna Redux Saga için kod parçacıkları sağlıyorum:

// actions.js

export const showNotification = (id, text) => {
  return { type: 'SHOW_NOTIFICATION', id, text }
}

export const hideNotification = (id) => {
  return { type: 'HIDE_NOTIFICATION', id }
}

export const showNotificationWithTimeout = (text) => {
  return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}

Eylemler basit ve saftır.

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Bileşen ile özel bir şey yok.

// sagas.js

import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'

// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
  const id = nextNotificationId++
  yield put(showNotification(id, action.text))
  yield delay(5000)
  yield put(hideNotification(id))
}

// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
  yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}

export default notificationSaga

Sagalar ES6 Jeneratörlerine dayanıyor

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

Redux Thunk ile karşılaştırıldığında

Artıları

  • Geri arama cehennemine kapılmıyorsunuz.
  • Eşzamansız akışlarınızı kolayca test edebilirsiniz.
  • Hareketleriniz saf kalır.

Eksileri

  • Nispeten yeni olan ES6 Jeneratörlerine bağlıdır.

Yukarıdaki kod parçacıkları tüm sorularınızı yanıtlamıyorsa lütfen çalıştırılabilir projeye bakın .


23

Bunu redux-thunk ile yapabilirsiniz . Redux belgesinde setTimeout gibi zaman uyumsuz eylemler için bir kılavuz vardır .


Sadece hızlı bir takip soru, Middleware kullanırken applyMiddleware(ReduxPromise, thunk)(createStore)bu thunk çalışma almak gibi olamaz gibi birkaç middleware (koma ayrılmış?) Eklemek nasıl.
Ilja

1
@Ilja Çalışmalı:const store = createStore(reducer, applyMiddleware([ReduxPromise, thunk]));
geniuscarrier

22

SAM desenine de göz atmanızı tavsiye ederim .

SAM modeli, model güncellendikten sonra (SAM modeli ~ düşürücü durumu + mağaza) "bildirimler 5 saniye sonra otomatik olarak kaybolur" gibi (otomatik) eylemlerin tetiklendiği bir "sonraki eylem tahmini" eklemeyi savunur.

Model, sıralı eylemleri ve model mutasyonlarını birer birer savunur, çünkü modelin "kontrol durumu" hangi eylemlerin etkinleştirildiğini ve / veya bir sonraki eylem yüklemi tarafından otomatik olarak yürütüldüğünü "kontrol eder". Bir eylemi işlemeden önce sistemin hangi durumda olacağını ve dolayısıyla bir sonraki beklenen eyleminize izin verilip verilmeyeceğini / mümkün olup olmadığını tahmin edemezsiniz.

Örneğin, kod,

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

bir hideNotification eyleminin gönderilebileceği gerçeği, "showNotication: true" değerini başarıyla kabul eden modele bağlı olduğundan SAM ile izin verilmez. Modelin onu kabul etmesini engelleyen başka kısımları olabilir ve bu nedenle hideNotification eylemini tetiklemek için hiçbir neden yoktur.

Mağaza güncellemeleri ve modelin yeni kontrol durumu bilindikten sonra uygun bir sonraki eylem yüklemesinin uygulanmasını şiddetle tavsiye ederim. Aradığınız davranışı uygulamanın en güvenli yolu budur.

İsterseniz Gitter'da bize katılabilirsiniz. Burada ayrıca bir SAM başlangıç ​​kılavuzu da bulunmaktadır .


Şimdiye kadar sadece yüzeyi çizdim, ama SAM deseni beni çok heyecanlandırdı. V = S( vm( M.present( A(data) ) ), nap(M))sadece güzel. Düşüncelerinizi ve deneyimlerinizi paylaştığınız için teşekkür ederiz. Daha derine ineceğim.

@ftor, teşekkür ederim! ilk yazdığımda da aynı duyguyu yaşadım. SAM'ı neredeyse bir yıldır üretimde kullandım ve SAM'ı uygulamak için bir kütüphaneye ihtiyacım olduğunu hissettiğim bir zamanı düşünemiyorum (hatta vdom, ne zaman kullanılabileceğini görebiliyorum). Sadece bir satır kod, hepsi bu! SAM izomorfik kod üretir, zaman uyumsuz çağrılarla nasıl başa çıkılacağı konusunda bir belirsizlik yoktur ... Yine de nerede olduğumu düşünemiyorum, ne yapıyorum?
metaprogrammer

SAM gerçek bir Yazılım Mühendisliği modelidir (onunla bir Alexa SDK üretti). TLA + 'ya dayalıdır ve bu inanılmaz çalışmanın gücünü her geliştiriciye getirmeye çalışır. SAM, herkesin on yıllardır kullandığı üç yaklaşımı düzeltir: - eylemler uygulama durumunu manipüle edebilir - atamalar mutasyona eşdeğerdir - bir programlama adımının ne olduğuna dair kesin bir tanım yoktur (örn. A = b * ca adımı) , 1 / okuma b'nin her 2 / hesaplama b * c sonuçla 3 / atama üç farklı adımlar c?
metaprogrammer

20

Çeşitli popüler yaklaşımları (aksiyon yaratıcıları, thunks, sagas, destanlar, efektler, özel ara katman yazılımı) denedikten sonra, belki de iyileştirme için yer olduğunu hissettim, bu yüzden bu blog makalesinde yolculuğumu belgeledim, iş mantığımı nereye koyacağım React / Redux uygulaması mı?

Buradaki tartışmalara çok benzer şekilde, çeşitli yaklaşımları karşılaştırmaya ve karşılaştırmaya çalıştım. Sonunda beni destanlardan, destanlardan, özel ara katman yazılımlarından ilham alan yeni bir kütüphane redux-mantığı tanıttı .

Zaman uyumsuz ES'yi gerçekleştirmek için bir yol sağlamanın yanı sıra, doğrulama, doğrulama, yetkilendirme eylemlerini durdurmanıza olanak tanır.

Bazı ortak işlevler, sadece en son istek (takeLatest) tarafından verilen yanıt kullanılarak, kaldırma, azaltma, iptal etme gibi basit bir şekilde bildirilebilir. redux-logic sizin için bu işlevselliği sağlayan kodunuzu sarar.

Bu, temel iş mantığınızı istediğiniz gibi uygulamanızı sağlar. İstemediğiniz sürece gözlemlenebilir veya jeneratör kullanmanız gerekmez. İşlevleri ve geri çağrıları, vaatleri, zaman uyumsuz işlevleri (zaman uyumsuz / beklemede) vb. Kullanın.

Basit bir 5s bildirimi yapmak için kod şöyle olacaktır:

const notificationHide = createLogic({
  // the action type that will trigger this logic
  type: 'NOTIFICATION_DISPLAY',
  
  // your business logic can be applied in several
  // execution hooks: validate, transform, process
  // We are defining our code in the process hook below
  // so it runs after the action hit reducers, hide 5s later
  process({ getState, action }, dispatch) {
    setTimeout(() => {
      dispatch({ type: 'NOTIFICATION_CLEAR' });
    }, 5000);
  }
});
    

Repo'mda, ekranın N öğeyle sınırlandırılabileceği ve sıraya giren herhangi biriyle dönebileceği Sebastian Lorber'in açıkladığı şeye benzer daha gelişmiş bir bildirim örneğim var. redux-logic bildirim örneği

Bir çeşitliliği var redux-mantık jsfiddle canlı örnekler yanı sıra tam örnekler . Dokümanlar ve örnekler üzerinde çalışmaya devam ediyorum.

Geri bildirimlerinizi duymak isterim.


Kütüphanenizi sevdiğimden emin değilim ama makalenizi beğeniyorum! Aferin adamım! Başkalarının zamanından tasarruf etmek için yeterli iş yaptınız.
Tyler Long

2
Burada redux-logic için örnek bir proje oluşturdum: github.com/tylerlong/hello-async/tree/master/redux-logic Bence bu iyi tasarlanmış bir yazılım parçası ve diğerlerine göre büyük dezavantajlar görmüyorum alternatifler.
Tyler Long

9

Bu sorunun biraz eski olduğunu anlıyorum ama redux-gözlemlenebilir aka kullanarak başka bir çözüm sunacağım . Epik.

Resmi belgelerin alıntılanması:

Redux-gözlemlenebilir nedir?

Redux için RxJS 5 tabanlı ara katman yazılımı. Yan etkiler oluşturmak ve daha fazlası için zaman uyumsuz eylemler oluşturun ve iptal edin.

Bir Destan, redux-gözlemlenebilirliğin temel ilkesidir.

Bir eylem akışı alan ve bir eylem akışı döndüren bir işlevdir. Eylemler, eylemler.

Daha fazla veya daha az kelimeyle, Akış yoluyla eylemler alan bir işlev oluşturabilir ve ardından yeni bir eylem akışı döndürebilirsiniz (zaman aşımları, gecikmeler, aralıklar ve istekler gibi yaygın yan etkileri kullanarak).

Kodu göndereyim ve sonra biraz daha açıklayayım

store.js

import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000

const initialState = ''
const rootReducer = (state = initialState, action) => {
  const {type, message} = action
  console.log(type)
  switch(type) {
    case NEW_NOTIFICATION:
      return message
    break
    case QUIT_NOTIFICATION:
      return initialState
    break
  }

  return state
}

const rootEpic = (action$) => {
  const incoming = action$.ofType(NEW_NOTIFICATION)
  const outgoing = incoming.switchMap((action) => {
    return Observable.of(quitNotification())
      .delay(NOTIFICATION_TIMEOUT)
      //.takeUntil(action$.ofType(NEW_NOTIFICATION))
  });

  return outgoing;
}

export function newNotification(message) {
  return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
  return ({type: QUIT_NOTIFICATION, message});
}

export const configureStore = () => createStore(
  rootReducer,
  applyMiddleware(createEpicMiddleware(rootEpic))
)

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'

const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

App.js

import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'

class App extends Component {

  render() {
    return (
      <div className="App">
        {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
        <button onClick={this.props.onNotificationRequest}>Click!</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notificationExistance : state.length > 0,
    notificationMessage : state
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

Bu sorunu çözmek için anahtar kodu, gördüğünüz kadar kolay, diğer cevaplardan farklı görünen tek şey rootEpic işlevidir.

Nokta 1: Sagalarda olduğu gibi, bir eylem akışı alan ve bir eylem akışı döndüren bir üst düzey işlev elde etmek için destanları birleştirmeniz gerekir, böylece onu createEpicMiddleware ara katman fabrikası ile kullanabilirsiniz . Bizim durumumuzda sadece birine ihtiyacımız var, bu yüzden sadece rootEpic'imiz var, bu yüzden hiçbir şey birleştirmek zorunda değiliz ama gerçeği bilmek güzel.

Nokta 2. Yan etkiler mantığını önemseyen rootEpic kodumuz sadece yaklaşık 5 satır kod alır, bu da harika! Oldukça açıklayıcı olduğu gerçeği dahil!

Nokta 3. Satır satır kök Kök açıklama (yorumlarda)

const rootEpic = (action$) => {
  // sets the incoming constant as a stream 
  // of actions with  type NEW_NOTIFICATION
  const incoming = action$.ofType(NEW_NOTIFICATION)
  // Merges the "incoming" stream with the stream resulting for each call
  // This functionality is similar to flatMap (or Promise.all in some way)
  // It creates a new stream with the values of incoming and 
  // the resulting values of the stream generated by the function passed
  // but it stops the merge when incoming gets a new value SO!,
  // in result: no quitNotification action is set in the resulting stream
  // in case there is a new alert
  const outgoing = incoming.switchMap((action) => {
    // creates of observable with the value passed 
    // (a stream with only one node)
    return Observable.of(quitNotification())
      // it waits before sending the nodes 
      // from the Observable.of(...) statement
      .delay(NOTIFICATION_TIMEOUT)
  });
  // we return the resulting stream
  return outgoing;
}

Umut ediyorum bu yardım eder!


Belirli api yöntemlerinin burada ne yaptığını açıklayabilir misiniz switchMap?
Dmitri Zaitsev

1
Windows'taki React Native uygulamamızda redux-gözlemlenebilir kullanıyoruz. Karmaşık, son derece eşzamansız bir soruna zarif bir uygulama çözümüdür ve Gitter kanalı ve GitHub sorunları aracılığıyla harika bir desteğe sahiptir. Ekstra karmaşıklık katmanı sadece elbette çözmesi gereken tam probleme ulaşırsanız buna değer.
Matt Hargett

8

Neden bu kadar zor olmalı? Sadece UI mantığı. Bildirim verilerini ayarlamak için özel bir işlem kullanın:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

ve görüntülemek için özel bir bileşen:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

Bu durumda sorular "eski durumu nasıl temizlersiniz?", "Bir bileşenin zamanın değiştiğini nasıl bildireceğimi" olmalıdır.

SetTimeout üzerinde bir bileşenden gönderilen bazı TIMEOUT eylemini uygulayabilirsiniz.

Belki de yeni bir bildirim gösterildiğinde bunu temizlemek iyi olur.

Her neyse, bir setTimeoutyerlerde olmalı , değil mi? Neden bir bileşende yapmıyorsunuz?

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

Motivasyon, "bildirim kaybolması" işlevinin gerçekten bir kullanıcı arayüzü sorunu olmasıdır. Böylece iş mantığınız için testi basitleştirir.

Nasıl uygulandığını test etmek mantıklı görünmüyor. Yalnızca bildirimin ne zaman zaman aşımına uğraması gerektiğini doğrulamak mantıklıdır. Böylece saplama için daha az kod, daha hızlı testler, daha temiz kod.


1
Bu en iyi cevap olmalı.
mmla

6

Seçmeli eylemlerde zaman aşımı yönetimi istiyorsanız, ara katman yazılımı yaklaşımını deneyebilirsiniz . Söz tabanlı eylemleri seçici olarak ele almak için benzer bir sorunla karşılaştım ve bu çözüm daha esnekti.

Diyelim ki aksiyon oluşturucunuz şöyle görünüyor:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

zaman aşımı yukarıdaki eylemde birden çok değer tutabilir

  • ms cinsinden sayı - belirli bir zaman aşımı süresi için
  • true - sabit bir zaman aşımı süresi için. (ara katman yazılımında işlenir)
  • undefined - hemen sevkiyat için

Ara katman yazılımı uygulamanız şöyle görünecektir:

//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {

  //If your action doesn't have any timeout attribute, fallback to the default handler
  if(!action.timeout) {
    return next (action)
  }

  const defaultTimeoutDuration = 1000;
  const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;

//timeout here is called based on the duration defined in the action.
  setTimeout(() => {
    next (action)
  }, timeoutDuration)
}

Artık tüm eylemlerinizi redux kullanarak bu ara katman katmanı üzerinden yönlendirebilirsiniz.

createStore(reducer, applyMiddleware(timeoutMiddleware))

Benzer örnekleri burada bulabilirsiniz


5

Bunu yapmanın uygun yolu kullanıyor Redux Thunk Redux Thunk belgelendirmesi olarak Redux için popüler katman ise,:

"Redux Thunk ara katmanı, eylem yerine bir işlev döndüren eylem oluşturucuları yazmanıza olanak tanır. Thunk, bir eylemin gönderilmesini geciktirmek veya yalnızca belirli bir koşul karşılandığında gönderilmek üzere kullanılabilir. İç işlev mağaza yöntemlerini alır msgstr "Parametreleri olarak getState komutunu gönder".

Temelde bir işlev döndürür ve gönderimi geciktirebilir veya bir koşul durumuna getirebilirsiniz.

Yani böyle bir şey işi sizin için yapacak:

import ReduxThunk from 'redux-thunk';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 5000);
  };
}

4

Basit. Trim-redux paketini kullanın ve böyle bir componentDidMountyere veya başka bir yere yazın ve öldürün componentWillUnmount.

componentDidMount() {
  this.tm = setTimeout(function() {
    setStore({ age: 20 });
  }, 3000);
}

componentWillUnmount() {
  clearTimeout(this.tm);
}

3

Redux'un kendisi oldukça ayrıntılı bir kütüphanedir ve bu tür şeyler için bir işlev verecek Redux-thunk gibi bir şey kullanmanız gerekir dispatch, böylece birkaç saniye sonra bildirimin kapanışını gönderebilirsiniz.

Ayrıntı ve uyumluluk gibi sorunları gidermek için bir kütüphane oluşturdum ve örneğiniz aşağıdaki gibi görünecek:

import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';

const notifications = createSyncTile({
  type: ['ui', 'notifications'],
  fn: ({ params }) => params.data,
  // to have only one tile for all notifications
  nesting: ({ type }) => [type],
});

const notificationsManager = createTile({
  type: ['ui', 'notificationManager'],
  fn: ({ params, dispatch, actions }) => {
    dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
    await sleep(params.timeout || 5000);
    dispatch(actions.ui.notifications({ type: params.type, data: null }));
    return { closed: true };
  },
  nesting: ({ type }) => [type],
});

Bu nedenle, zaman uyumsuz eylem içinde bildirimleri göstermek için arka plan hakkında bazı bilgiler isteyebilecek veya daha sonra bildirimin manuel olarak kapatılıp kapatılmadığını kontrol edebilecek senkronizasyon eylemleri oluşturuyoruz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.