Jak wysłać akcję Redux z limitem czasu?

Mam działanie, które aktualizuje stan powiadomień mojej aplikacji. Zazwyczaj to powiadomienie będzie błędem lub informacją pewnego rodzaju. Muszę następnie wysłać kolejną akcję po 5 sekundach, które zwrócą stan powiadomienia do początkowego, więc nie ma powiadomienia. Głównym powodem tego jest zapewnienie funkcjonalności, w której powiadomienia znikają automatycznie po 5 sekundach.

Nie miałem szczęścia z użyciem setTimeout i zwróceniem kolejnej akcji i nie mogę znaleźć jak to się robi online. Więc każda rada jest mile widziana.

Author: qbolec, 2016-02-15

13 answers

Nie wpadaj w pułapkę myślenia, że biblioteka powinna przepisywać, jak robić wszystko. Jeśli chcesz zrobić coś z limitem czasu w JavaScript, musisz użyć setTimeout. Nie ma powodu, dla którego działania Redux powinny być inne.

Redux oferuje alternatywne sposoby radzenia sobie z asynchronicznymi rzeczami, ale powinieneś używać ich tylko wtedy, gdy zdajesz sobie sprawę, że powtarzasz za dużo kodu. Jeśli nie masz tego problemu, użyj tego, co oferuje Język i idź dla najprostszego rozwiązania.

Pisanie Kodu Asynchronicznego Inline

To najprostszy sposób. I nie ma tu nic konkretnego o Reduxie.
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Podobnie, z wnętrza połączonego komponentu:

this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Jedyną różnicą jest to, że w połączonym komponencie zwykle nie masz dostępu do samego sklepu, ale dostajesz albo dispatch(), albo konkretnych twórców akcji wstrzykniętych jako rekwizyty. Jednak to nie robi dla nas żadnej różnicy.

Jeśli nie lubisz tworzenie literówek podczas wysyłania tych samych akcji z różnych komponentów, można wyodrębnić twórców akcji zamiast wysyłania obiektów akcji w wierszu:

// 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)

Lub, jeśli wcześniej związałeś je z connect():

this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

Do tej pory nie używaliśmy żadnego oprogramowania pośredniczącego ani innej zaawansowanej koncepcji.

Extracting Async Action Creator

Powyższe podejście działa dobrze w prostych przypadkach, ale może się okazać, że ma kilka problemów: {]}

  • It zmusza cię do powielenia tej logiki w dowolnym miejscu, w którym chcesz wyświetlić powiadomienie.
  • powiadomienia nie mają identyfikatorów, więc będziesz mieć stan wyścigu, jeśli pokażesz dwa powiadomienia wystarczająco szybko. Gdy skończy się pierwszy limit czasu, wyśle HIDE_NOTIFICATION, błędnie ukrywając drugie powiadomienie wcześniej niż po upływie limitu czasu.

Aby rozwiązać te problemy, musisz wyodrębnić funkcję, która centralizuje logikę limitu czasu i wysyła te dwie akcje. To może wyglądać jak to:

// 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 interval ID and call
  // clearInterval(), but we’d still want to do it in a single place.
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

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

Teraz komponenty mogą używać showNotificationWithTimeout bez powielania tej logiki lub posiadania warunków race z różnymi powiadomieniami:

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

Dlaczego showNotificationWithTimeout() przyjmuje dispatch jako pierwszy argument? Ponieważ musi wysyłać akcje do sklepu. Zwykle komponent ma dostęp do dispatch, ale ponieważ chcemy, aby zewnętrzna funkcja przejęła kontrolę nad wysyłaniem, musimy dać jej kontrolę nad wysyłaniem.

Jeśli masz sklep singleton wyeksportowany z jakiegoś modułu, możesz po prostu zaimportować go i dispatch bezpośrednio na nim zamiast:

// 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.')    

Wygląda to prościej, ale nie zalecamy takiego podejścia . Głównym powodem, dla którego go nie lubimy, jest to, że zmusza store do bycia singletonem. To sprawia, że bardzo trudno jest zaimplementować renderowanie serwera . Na serwerze będziesz chciał, aby każde żądanie miało swój własny sklep, aby różni użytkownicy otrzymywali różne wstępnie załadowane dane.

Sklep singleton również utrudnia testowanie. Nie możesz już makieta sklepu podczas testowania twórców akcji, ponieważ odwołują się do konkretnego prawdziwego sklepu wyeksportowanego z określonego modułu. Nie można nawet zresetować jego stanu z zewnątrz.

Więc chociaż technicznie możesz wyeksportować sklep singleton z modułu, zniechęcamy go. Nie rób tego, chyba że masz pewność, że Twoja aplikacja nigdy nie będzie dodawać renderowania serwera.

Powrót do poprzedniej wersji:

// 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.')    

To rozwiązuje problemy z powielaniem logiki i ratuje nas od rasy warunki.

Thunk Middleware

Dla prostych aplikacji, podejście powinno wystarczyć. Nie martw się o oprogramowanie pośrednie, jeśli jesteś z niego zadowolony.

W większych aplikacjach można jednak znaleźć pewne niedogodności wokół niego.

Na przykład, wydaje się niefortunne, że musimy przejść dispatch wokół. To sprawia, że trudniejsze jest oddzielenie kontenera i komponentów prezentacyjnych , ponieważ każdy komponent, który wysyła akcje Redux asynchronicznie w sposób powyżej musi zaakceptować dispatch jako rekwizyt, aby mógł przejść dalej. Nie można już łączyć twórców akcji z connect(), ponieważ showNotificationWithTimeout() tak naprawdę nie jest twórcą akcji. Nie zwraca akcji Redux.

Ponadto może być niezręcznie pamiętać, które funkcje są synchronicznymi twórcami akcji, jak showNotification(), a które asynchronicznymi pomocnikami, jak showNotificationWithTimeout(). Musisz używać ich inaczej i uważać, aby nie pomylić ich ze sobą.

To była motywacja do znalezienie sposobu, aby "legitymizować" ten wzór dostarczania dispatch do funkcji pomocniczej i pomóc Redux "widzieć" takich asynchronicznych twórców akcji jako szczególny przypadek zwykłych twórców akcji , a nie zupełnie różnych funkcji.

Jeśli nadal jesteś z nami i rozpoznajesz problem w swojej aplikacji, zapraszamy do korzystania z oprogramowania pośredniczącego Redux Thunk.

W gist, Redux Thunk uczy Redux rozpoznawać specjalne rodzaje działań, które są w rzeczywistości funkcje:

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)
})

Gdy ta middleware jest włączona, jeśli wyślesz funkcję , Redux Thunk middleware poda ją dispatch jako argument. Będzie również "połykać" takie akcje, więc nie martw się, że Twoje reduktory otrzymają dziwne argumenty funkcji. Twoje reduktory otrzymają tylko zwykłe akcje obiektu-albo emitowane bezpośrednio, albo emitowane przez funkcje, jak właśnie opisaliśmy.

To nie wygląda na użyteczne, prawda? Nie w tej konkretnej sytuacji. Jednak to możemy zadeklarować showNotificationWithTimeout() jako zwykłego twórcę akcji Redux:
// 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)
  }
}

Zauważ, że funkcja jest prawie identyczna z tą, którą napisaliśmy w poprzedniej sekcji. Jednak nie przyjmuje dispatch jako pierwszego argumentu. Zamiast tego zwraca funkcję, która przyjmuje dispatch jako pierwszy argument.

Jak użylibyśmy go w naszym komponencie? Na pewno możemy to napisać:

// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)

Nazywamy kreatora akcji asynchronicznej, aby uzyskać wewnętrzną funkcję, która chce tylko dispatch, a następnie mijamy dispatch.

Jednak jest to jeszcze bardziej niewygodne niż Oryginalna wersja! Dlaczego w ogóle poszliśmy w tamtą stronę?

Z powodu tego, co ci wcześniej powiedziałem. jeśli Redux Thunk middleware jest włączone, za każdym razem, gdy spróbujesz wysłać funkcję zamiast obiektu akcji, middleware wywoła tę funkcję za pomocą samej metody dispatch jako pierwszego argumentu.

Więc możemy zrobić to zamiast:

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

Wreszcie, wysyłanie asynchronicznego akcja (tak naprawdę seria akcji) nie wygląda inaczej niż wysyłanie jednej akcji synchronicznie do komponentu. Co jest dobre, ponieważ komponenty nie powinny dbać o to, czy coś dzieje się synchronicznie lub asynchronicznie. Po prostu to usunęliśmy.

Zauważ, że ponieważ" uczyliśmy "Redux rozpoznawania takich" specjalnych " twórców akcji (nazywamy ich {149]}thunk {48]} twórcami akcji), możemy teraz używać ich w dowolnym miejscu, w którym będziemy używać zwykłych twórców akcji. Na przykład, możemy użyj ich z 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)

Stan odczytu w Thunks

Zazwyczaj reduktory zawierają logikę biznesową do określania następnego stanu. Jednak reduktory uruchamiają się dopiero po wysłaniu akcji. Co zrobić, jeśli masz efekt uboczny (na przykład wywołanie interfejsu API) w kreatorze akcji thunk i chcesz mu zapobiec pod pewnymi warunkami?

Bez użycia thunk middleware, wykonałbyś to sprawdzenie wewnątrz komponentu:]}
// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

Jednakże, punkt wyodrębnienie kreatora akcji miało scentralizować tę powtarzalną logikę w wielu komponentach. Na szczęście Redux Thunk oferuje Ci sposób na przeczytanie aktualnego stanu sklepu Redux. Oprócz dispatch, przekazuje również getState jako drugi argument do funkcji, którą zwracasz od twórcy akcji thunk. Pozwala to thunk odczytać aktualny stan sklepu.

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)
  }
}
Nie nadużywaj tego wzoru. Jest to dobre dla ratowania z wywołań API, gdy są buforowane dane dostępne, ale nie jest to bardzo dobry fundament do budowania logiki biznesowej. Jeśli używasz getState() tylko do warunkowego wysyłania różnych akcji, rozważ umieszczenie logiki biznesowej w reduktorach.

Następne Kroki

Teraz, gdy masz podstawową intuicję o tym, jak thunks działa, sprawdź Redux asynchroniczny przykład , który ich używa.

Możesz znaleźć wiele przykładów, w których thunks zwraca obietnice. Nie jest to wymagane, ale może być bardzo wygodne. Redux nie obchodzi, co zwracasz z thunk, ale daje Ci swoją wartość zwrotu z dispatch(). Dlatego możesz zwrócić obietnicę z thunk i poczekać na jej wypełnienie, dzwoniąc dispatch(someThunkReturningPromise()).then(...).

Można również podzielić twórców złożonych akcji thunk na kilka mniejszych twórców akcji thunk. Metoda dispatch dostarczona przez thunksa może zaakceptować thunksa, więc można zastosować wzór rekurencyjnie. Ponownie, działa to najlepiej z obietnic, ponieważ można zaimplementować asynchroniczny przepływ sterowania na górze to.

W przypadku niektórych aplikacji może się zdarzyć, że wymagania dotyczące asynchronicznego przepływu sterowania są zbyt złożone, aby można je było wyrazić za pomocą thunksów. Na przykład ponowne Próbowanie nieudanych żądań, ponowne autoryzowanie za pomocą tokenów lub wprowadzanie krok po kroku może być zbyt gadatliwe i podatne na błędy, gdy jest napisane w ten sposób. W tym przypadku warto przyjrzeć się bardziej zaawansowanym rozwiązaniom asynchronicznego przepływu sterowania, takim jak Redux Saga lub Redux Loop. Oceń je, porównaj przykłady odpowiadające Twoim potrzebom i wybierz ten, który najbardziej Ci się podoba.

Na koniec, nie używaj niczego (w tym thunksów), jeśli nie masz prawdziwej potrzeby ich używania. Pamiętaj, że w zależności od wymagań Twoje rozwiązanie może wyglądać tak prosto, jak]}

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Nie przejmuj się, jeśli nie wiesz, dlaczego to robisz.
 2066
Author: Dan Abramov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-20 02:28:06

Używając Redux-saga

Jak powiedział Dan Abramov, jeśli chcesz bardziej zaawansowanej kontroli nad kodem asynchronicznym, możesz spojrzeć naredux-saga .

Ta odpowiedź jest prostym przykładem, jeśli chcesz lepiej wyjaśnić, dlaczego redux-saga może być przydatny dla Twojej aplikacji, Sprawdź ta druga odpowiedź .

Ogólna idea jest taka, że Redux-saga oferuje interpreter generatorów ES6, który pozwala na łatwe pisanie kodu asynchronicznego, który wygląda jak synchroniczny kod (dlatego w Redux-saga często znajdziesz pętle infinite while). W jakiś sposób Redux-saga buduje swój własny język bezpośrednio wewnątrz Javascript. Redux-saga może wydawać się trochę trudne do nauczenia się na początku, ponieważ potrzebujesz podstawowego zrozumienia generatorów, ale także zrozumieć język oferowany przez Redux-saga.

Postaram się tutaj opisać system powiadomień, który zbudowałem na bazie redux-saga. Ten przykład jest obecnie produkowany.

Zaawansowany system powiadomień Specyfikacja

  • możesz poprosić o wyświetlenie powiadomienia
  • możesz poprosić o powiadomienie, aby ukryć
  • powiadomienie nie powinno być wyświetlane dłużej niż 4 sekundy
  • wiele powiadomień może być wyświetlanych w tym samym czasie
  • nie więcej niż 3 powiadomienia mogą być wyświetlane w tym samym czasie
  • Jeśli wymagane jest powiadomienie, gdy są już 3 wyświetlane powiadomienia, Kolejka / przesuń to.

Wynik

Zrzut ekranu mojej aplikacji produkcyjnej Stample.co

toasty

Kod

Tutaj nazwałem powiadomienie toast ale to jest Szczegóły nazewnictwa.

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)
    ]
}

I reduktor:

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;
    }
};

Użycie

Możesz po prostu wysłać TOAST_DISPLAY_REQUESTED zdarzenia. Jeśli wyślesz 4 żądania, wyświetlone zostaną tylko 3 powiadomienia, a czwarty pojawi się nieco później, gdy zniknie pierwsze powiadomienie.

Zauważ, że Nie polecam wysyłania TOAST_DISPLAY_REQUESTED z JSX. Wolisz dodać kolejną sagę, która nasłuchuje już istniejących zdarzeń aplikacji, a następnie wysłać TOAST_DISPLAY_REQUESTED: twój komponent, który uruchamia powiadomienie, nie musi być ściśle powiązany z systemem powiadomień.

Podsumowanie

Mój kod nie jest idealny, ale działa w produkcji z 0 błędami przez miesiące. Redux - Saga i generatory są początkowo trochę trudne, ale gdy je zrozumiesz, ten rodzaj systemu jest dość łatwy do budowy.

Można nawet łatwo zaimplementować bardziej złożone reguły, takie jak:]}
  • gdy zbyt wiele powiadomień jest "w kolejce", daj mniej czasu wyświetlania dla każdego powiadomienia, aby rozmiar kolejki mógł się szybciej zmniejszać.
  • wykrywa zmiany rozmiaru okna i odpowiednio zmienia maksymalną liczbę wyświetlanych powiadomień (na przykład pulpit=3, portret telefonu = 2, krajobraz telefonu = 1)

Honnestly, powodzenia w realizacji tego typu rzeczy prawidłowo z thunks.

Uwaga możesz zrobić dokładnie to samo z redux-observable , który jest bardzo podobny do redux-saga. To prawie to samo i to kwestia gustu między generatorami a RxJS.

 153
Author: Sebastien Lorber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 15:10:57

Polecam również przyjrzeć się wzorcowi SAM .

Wzorzec SAM zaleca włączenie "Next-action-predicate", w którym (automatyczne) akcje, takie jak "powiadomienia znikają automatycznie po 5 sekundach"są wyzwalane po zaktualizowaniu modelu (model sam ~ stan reduktora + sklep).

Wzorzec opowiada się za sekwencjonowaniem akcji i mutacją modelu pojedynczo, ponieważ "stan sterowania" modelu "kontroluje", które akcje są włączone i / lub automatycznie wykonywane przez predykat następnej akcji. Po prostu nie można przewidzieć (ogólnie), jaki stan będzie system przed przetworzeniem akcji, a tym samym, czy następna oczekiwana akcja będzie dozwolona / możliwa.

Więc na przykład kod,

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

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

Nie byłoby dozwolone z SAM, ponieważ fakt, że może być wywołana akcja hideNotification jest zależny od modelu pomyślnie przyjmującego wartość "showNotication: true". Mogą być inne części model, który uniemożliwia jej akceptację i w związku z tym nie byłoby powodu do uruchomienia akcji hidenotify.

Gorąco polecam wdrożenie odpowiedniego predykatu następnej akcji po aktualizacji sklepu i poznaniu nowego stanu kontroli modelu. To najbezpieczniejszy sposób na wdrożenie zachowania, którego szukasz.

Możesz dołączyć do nas na Gitterze, jeśli chcesz. Istnieje również przewodnik sam getting started dostępny tutaj.
 19
Author: Jean-Jacques Dubray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-24 02:31:26

Możesz to zrobić za pomocą redux-thunk . W dokumencie redux znajduje się instrukcja dla akcji asynchronicznych, takich jak setTimeout.

 19
Author: Fatih Erikli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 13:35:34

Repozytorium z przykładowymi projektami

Obecnie istnieją cztery przykładowe projekty:

  1. Zapis Kodu Asynchronicznego Inline
  2. Extracting Async Action Creator
  3. Użyj Redux Thunk
  4. Użyj Redux Saga

Przyjęta odpowiedź jest niesamowita.

Ale czegoś brakuje:

  1. żadnych przykładowych projektów, tylko fragmenty kodu.
  2. brak przykładowego kodu dla innych alternatywy, takie jak:
    1. Redux Saga

Więc utworzyłem Hello Async repozytorium, aby dodać brakujące rzeczy:

  1. prowadzenie projektów. Można je pobierać i uruchamiać bez modyfikacji.
  2. podaj przykładowy kod, aby uzyskać więcej alternatyw:

Redux Saga

Zaakceptowana odpowiedź zawiera już przykładowy kod urywki dla kodu Async Inline, Async Action Generator i Redux Thunk. Dla kompletności podaję fragmenty kodu do Redux Saga:

// 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 }
}

Działania są proste i czyste.

// component.js

import { connect } from 'react-redux'

// ...

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

// ...

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

Nic nie jest wyjątkowe z komponentem.

// 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

Sagaform bazuje na generatorach ES6

// index.js

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

const sagaMiddleware = createSagaMiddleware()

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

sagaMiddleware.run(saga)

W porównaniu do Redux Thunk

Plusy

    Nie skończysz w piekle oddzwaniania.
  • możesz łatwo przetestować swoje asynchroniczne przepływy.
  • Twój działania pozostają czyste.

Cons

  • zależy od generatorów ES6, które są stosunkowo nowe.

Proszę zapoznać się z runnable project jeśli powyższy fragment kodu nie odpowiada na wszystkie Twoje pytania.

 18
Author: Tyler Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-23 06:26:26

Po wypróbowaniu różnych popularnych podejść (twórcy akcji, thunksy, sagi, epiki, efekty, custom middleware), nadal czułem, że może jest miejsce na poprawę, więc udokumentowałem moją podróż w tym artykule na blogu, Gdzie umieścić moją logikę biznesową w aplikacji React/Redux?

Podobnie jak w dyskusjach tutaj, starałem się kontrastować i porównywać różne podejścia. Ostatecznie doprowadziło mnie to do wprowadzenia nowej biblioteki redux-logic , która czerpie inspirację z epiki, sagi, custom middleware.

Umożliwia przechwytywanie działań w celu walidacji, weryfikacji, autoryzacji, a także zapewnia sposób wykonywania asynchronicznych IO.

Niektóre popularne funkcje można po prostu zadeklarować, jak debouncing, Dławienie, anulowanie, i tylko za pomocą odpowiedzi z ostatniego żądania (takeLatest). redux-logic owija kod dostarczając tę funkcjonalność dla Ciebie.

To uwalnia cię do wdrożenia podstawowej logiki biznesowej, jak chcesz. Nie musisz. używaj obserwabli lub generatorów, chyba że chcesz. Użyj funkcji i wywołań zwrotnych, obietnic, funkcji asynchronicznych (async / wait), itp.

Kod do robienia prostego powiadomienia 5s to coś w stylu:

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);
  }
});
    

Mam bardziej zaawansowany przykład powiadomień w moim repo, który działa podobnie do tego, co opisał Sebastian Lorber, gdzie można ograniczyć wyświetlanie do n elementów i obracać dowolne, które ustawiono w kolejce. redux-przykład powiadomienia logicznego

I posiada wiele przykładów redux-logic jsfiddle na żywo, jak również pełne przykłady . Kontynuuję pracę nad dokumentami i przykładami.

Chciałbym usłyszeć Twoją opinię.
 16
Author: Jeff Barczewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 01:31:52

Rozumiem, że to pytanie jest trochę stare, ale zamierzam wprowadzić inne rozwiązanie używając redux-observable aka. Epickie.

Cytując oficjalną dokumentację:

Co to jest redux-obserwowalny?

Rxjs 5-oparte oprogramowanie pośredniczące dla Redux. Komponuj i anuluj akcje asynchroniczne do tworzenie efektów ubocznych i nie tylko.

Epik jest podstawowym prymitywem redux-obserwowalnym.

Jest to funkcja, która przyjmuje strumień akcji i zwraca strumień działań. Działania w, działania na zewnątrz.

W mniej więcej słowach można utworzyć funkcję, która odbiera akcje za pośrednictwem strumienia, a następnie zwrócić nowy strumień akcji (używając typowych efektów ubocznych, takich jak ograniczenia czasowe, opóźnienia, interwały i żądania).

Pozwól mi opublikować kod, a następnie wyjaśnić trochę więcej na ten temat

Sklep.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))
)

Indeks.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)

Kluczowy kod do rozwiązania tego problemu jest tak prosty, jak widać, jedyną rzeczą, która wydaje się inna od innych odpowiedzi, jest funkcja rootEpic.

Punkt 1. Podobnie jak w przypadku sagi, musisz połączyć epiki, aby uzyskać funkcję najwyższego poziomu, która odbiera strumień akcji i zwraca strumień akcji, więc możesz jej używać z fabryką middleware createEpicMiddleware {36]}. W naszym przypadku potrzebujemy tylko jednego, więc mamy tylko nasz rootEpic , więc nie musimy niczego łączyć, ale dobrze jest wiedzieć fakt.

Punkt 2. Nasze rootEpic który dba o skutki uboczne logika zajmuje tylko około 5 linijek kodu, co jest niesamowite! W tym fakt, że jest dość deklaratywny!

Punkt 3. Linia po linii Wyjaśnienie rootEpic (w komentarzach)

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;
}
Mam nadzieję, że to pomoże!
 7
Author: cnexans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-11 12:52:21

Jeśli chcesz obsłużyć timeout przy selektywnych akcjach, możesz wypróbować podejściemiddleware . Miałem podobny problem z selektywną obsługą działań opartych na obietnicach, a to rozwiązanie było bardziej elastyczne.

Powiedzmy, że Twój twórca akcji wygląda tak:

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

Timeout może zawierać wiele wartości w powyższej akcji

  • liczba w ms - dla określonego czasu trwania
  • true-dla stałego limitu czasu. (obsługiwane w middleware)
  • undefined-do natychmiastowej wysyłki

Twoja implementacja middleware wyglądałaby tak:

//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)
}

Możesz teraz przekierować wszystkie swoje działania przez tę warstwę oprogramowania pośredniego za pomocą redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

Możesz znaleźć kilka podobnych przykładów tutaj

 5
Author: Yash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-15 13:24:05

Dlaczego to ma być takie trudne? To tylko logika interfejsu użytkownika. Użyj dedykowanej akcji, aby ustawić dane powiadomień:

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

I dedykowany komponent do wyświetlania go:

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

W tym przypadku pytania powinny być "jak oczyścić stary stan?", "jak powiadomić komponent, że czas się zmienił"

Możesz zaimplementować pewną akcję TIMEOUT, która jest wysyłana na setTimeout z komponentu.

Może wystarczy go wyczyścić, gdy nowe powiadomienie jest pokazane.

W każdym razie, gdzieś powinny być jakieś setTimeout, prawda? Dlaczego nie zrobić tego w komponencie

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

Motywacją jest to, że funkcja "powiadomienia zanikają" jest naprawdę problemem interfejsu użytkownika. Upraszcza to więc testowanie logiki biznesowej.

Nie ma sensu testować, jak to jest zaimplementowane. Sensowne jest tylko sprawdzenie, kiedy powiadomienie powinno się zakończyć. W ten sposób mniej kodu do stub, szybsze testy, czystszy kod.

 3
Author: Vanuan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-27 12:14:32

Odpowiednim sposobem jest użycie Redux Thunk , który jest w tym samym czasie Redux stał się częścią Redux Thunk documentation.]}

"Redux Thunk middleware pozwala pisać twórców akcji, które zwraca funkcję zamiast akcji. Thunk może być użyty do opóźnienia wysłania czynności, lub do wysłania tylko wtedy, gdy określony warunek jest spełnione. Wewnętrzna funkcja odbiera metody przechowywania i getState jako parametry".

Więc zasadniczo zwraca funkcję i można opóźnić wysyłkę lub umieścić ją w stanie warunkowym.

Więc coś takiego zrobi za ciebie robotę:

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);
  };
}
 3
Author: Alireza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 12:09:12

Sam Redux jest dość gadatliwą biblioteką, A do takich rzeczy trzeba by użyć czegoś w rodzaju Redux-thunk , który da funkcję dispatch, dzięki czemu będziesz mógł wysłać zamknięcie powiadomienia po kilku sekundach.

Stworzyłem bibliotekę , aby rozwiązać problemy takie jak zwięzłość i kompozytowość, a twój przykład będzie wyglądał następująco:

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],
});

Więc komponujemy akcje synchronizacji do wyświetlania powiadomień wewnątrz akcji asynchronicznej, która może poproś o informacje w tle lub sprawdź później, czy powiadomienie zostało zamknięte ręcznie.

 1
Author: Bloomca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-04 15:16:26

To proste. użyj pakietu trim-redux i napisz w ten sposób w componentDidMout lub innym miejscu i zabij go w componentWillUnmount.

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

componentWillUnmount(){
   clearTimeout(this.tm);
}
 1
Author: Mohmmad Ebrahimi Aval,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-11 14:21:19

Gdy wykonujesz setTimeout, upewnij się, że również wyczyścisz timeout używając clearTimeout, gdy twój komponent un montuje się w metodzie cyklu życia componentWillUnMount

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
this.timeout = setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

componentWillUnMount(){
   clearTimeout(this.timeout);
}
 0
Author: Think-Twice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 04:06:35