Wie kann ich eine Redux-Aktion mit einer Zeitüberschreitung auslösen?


891

Ich habe eine Aktion, die den Benachrichtigungsstatus meiner Anwendung aktualisiert. Normalerweise handelt es sich bei dieser Benachrichtigung um einen Fehler oder eine Information. Ich muss dann nach 5 Sekunden eine weitere Aktion auslösen, die den Benachrichtigungsstatus auf den ursprünglichen Status zurücksetzt, also keine Benachrichtigung. Der Hauptgrund dafür ist die Bereitstellung von Funktionen, bei denen Benachrichtigungen nach 5 Sekunden automatisch ausgeblendet werden.

Ich hatte kein Glück damit, eine setTimeoutandere Aktion zu verwenden und zurückzugeben, und kann nicht herausfinden, wie dies online gemacht wird. Jeder Rat ist also willkommen.


30
Vergiss nicht, meine redux-sagaAntwort zu überprüfen , wenn du etwas Besseres als Thunks willst. Eine späte Antwort, sodass Sie lange scrollen müssen, bevor Sie sie sehen können :) bedeutet nicht, dass es nicht lesenswert ist. Hier ist eine Verknüpfung: stackoverflow.com/a/38574266/82609
Sebastien Lorber

5
Wann immer Sie setTimeout machen, vergessen Sie nicht, den Timer mit clearTimeout in der Lebenszyklusmethode componentWillUnMount zu löschen
Hemadri Dasari

2
Redux-Saga ist cool, aber sie scheinen keine Unterstützung für typisierte Antworten von Generatorfunktionen zu haben. Könnte wichtig sein, wenn Sie Typoskript mit Reagieren verwenden.
Crhistian Ramirez

Antworten:


2617

Fallen Sie nicht in die Falle, wenn Sie denken, eine Bibliothek sollte vorschreiben, wie alles zu tun ist . Wenn Sie etwas mit einer Zeitüberschreitung in JavaScript tun möchten, müssen Sie verwenden setTimeout. Es gibt keinen Grund, warum Redux-Aktionen anders sein sollten.

Redux nicht bieten einige alternative Möglichkeiten, mit asynchronen Sachen zu tun, aber Sie sollten nur diejenigen verwenden , wenn Sie merken , Sie sind zu viel Code wiederholen. Wenn Sie dieses Problem nicht haben, verwenden Sie das, was die Sprache bietet, und wählen Sie die einfachste Lösung.

Async-Code inline schreiben

Dies ist bei weitem der einfachste Weg. Und hier gibt es nichts Spezielles für Redux.

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

Ebenso aus dem Inneren einer verbundenen Komponente:

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

Der einzige Unterschied besteht darin, dass Sie in einer verbundenen Komponente normalerweise keinen Zugriff auf das Geschäft selbst haben, sondern entweder dispatch()oder bestimmte Aktionsersteller als Requisiten injiziert bekommen. Dies macht jedoch für uns keinen Unterschied.

Wenn Sie beim Versenden derselben Aktionen aus verschiedenen Komponenten keine Tippfehler machen möchten, möchten Sie möglicherweise Aktionsersteller extrahieren, anstatt Aktionsobjekte inline zu versenden:

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

Oder, wenn Sie sie zuvor gebunden haben mit connect():

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

Bisher haben wir keine Middleware oder ein anderes fortschrittliches Konzept verwendet.

Async Action Creator extrahieren

Der obige Ansatz funktioniert in einfachen Fällen einwandfrei, es kann jedoch vorkommen, dass einige Probleme auftreten:

  • Sie werden gezwungen, diese Logik überall dort zu duplizieren, wo Sie eine Benachrichtigung anzeigen möchten.
  • Die Benachrichtigungen haben keine IDs, sodass Sie eine Rennbedingung haben, wenn Sie zwei Benachrichtigungen schnell genug anzeigen. Wenn das erste Timeout abgelaufen ist, wird es versendet HIDE_NOTIFICATIONund die zweite Benachrichtigung wird fälschlicherweise früher als nach dem Timeout ausgeblendet.

Um diese Probleme zu lösen, müssten Sie eine Funktion extrahieren, die die Timeout-Logik zentralisiert und diese beiden Aktionen auslöst. Es könnte so aussehen:

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

Jetzt können Komponenten verwendet werden, showNotificationWithTimeoutohne diese Logik zu duplizieren oder Rennbedingungen mit unterschiedlichen Benachrichtigungen zu haben:

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

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

Warum showNotificationWithTimeout()akzeptiert dispatchals erstes Argument? Weil es Aktionen an das Geschäft senden muss. Normalerweise hat eine Komponente Zugriff auf, dispatchaber da eine externe Funktion die Kontrolle über den Versand übernehmen soll, müssen wir ihr die Kontrolle über den Versand geben.

Wenn Sie einen Singleton-Speicher aus einem Modul exportieren ließen, können Sie ihn einfach importieren und dispatchstattdessen direkt darauf:

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

Das sieht einfacher aus, aber wir empfehlen diesen Ansatz nicht . Der Hauptgrund, warum wir es nicht mögen, ist, dass es den Speicher zwingt, ein Singleton zu sein . Dies macht es sehr schwierig, das Server-Rendering zu implementieren . Auf dem Server soll jede Anforderung einen eigenen Speicher haben, damit verschiedene Benutzer unterschiedliche vorinstallierte Daten erhalten.

Ein Singleton-Store erschwert auch das Testen. Sie können einen Speicher nicht mehr verspotten, wenn Sie Aktionsersteller testen, da diese auf einen bestimmten realen Speicher verweisen, der aus einem bestimmten Modul exportiert wurde. Sie können den Status nicht einmal von außen zurücksetzen.

Obwohl Sie einen Singleton-Speicher technisch aus einem Modul exportieren können, raten wir davon ab. Tun Sie dies nur, wenn Sie sicher sind, dass Ihre App niemals Server-Rendering hinzufügt.

Zurück zur vorherigen Version:

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

Dies löst die Probleme mit der Verdoppelung der Logik und bewahrt uns vor den Rennbedingungen.

Thunk Middleware

Für einfache Apps sollte der Ansatz ausreichen. Machen Sie sich keine Sorgen um Middleware, wenn Sie damit zufrieden sind.

In größeren Apps können jedoch bestimmte Unannehmlichkeiten auftreten.

Zum Beispiel scheint es bedauerlich, dass wir herumreichen müssen dispatch. Dies macht es schwieriger, Container- und Präsentationskomponenten zu trennen, da jede Komponente, die Redux-Aktionen auf die oben beschriebene Weise asynchron auslöst, dispatchals Requisite akzeptiert werden muss, damit sie sie weitergeben kann. Sie können nicht mehr nur Aktionsersteller binden, connect()da dies showNotificationWithTimeout()nicht wirklich ein Aktionsersteller ist. Es wird keine Redux-Aktion zurückgegeben.

Darüber hinaus kann es schwierig sein, sich daran zu erinnern, welche Funktionen synchrone Aktionsersteller showNotification()und welche asynchrone Helfer sind showNotificationWithTimeout(). Sie müssen sie anders verwenden und darauf achten, sie nicht miteinander zu verwechseln.

Dies war die Motivation , einen Weg zu finden, um dieses Muster der Bereitstellung dispatcheiner Hilfsfunktion zu „legitimieren“ und Redux dabei zu helfen, solche asynchronen Aktionsersteller als Sonderfall normaler Aktionsersteller und nicht als völlig andere Funktionen zu „sehen“ .

Wenn Sie noch bei uns sind und auch ein Problem in Ihrer App erkennen, können Sie gerne die Redux Thunk- Middleware verwenden.

Kurz gesagt, Redux Thunk lehrt Redux, spezielle Arten von Aktionen zu erkennen, die tatsächlich Funktionen sind:

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

Wenn diese Middleware aktiviert ist und Sie eine Funktion versenden , gibt die Redux Thunk-Middleware diese dispatchals Argument an. Es wird auch solche Aktionen "schlucken", also machen Sie sich keine Sorgen, dass Ihre Reduzierer seltsame Funktionsargumente erhalten. Ihre Reduzierungen erhalten nur einfache Objektaktionen - entweder direkt oder von den gerade beschriebenen Funktionen.

Das sieht nicht sehr nützlich aus, oder? Nicht in dieser besonderen Situation. Wir können uns jedoch showNotificationWithTimeout()als regulärer Redux-Aktionsersteller deklarieren :

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

Beachten Sie, dass die Funktion fast identisch mit der im vorherigen Abschnitt beschriebenen ist. Es wird jedoch nicht dispatchals erstes Argument akzeptiert . Stattdessen es gibt eine Funktion , die übernimmt dispatchals erstes Argument.

Wie würden wir es in unserer Komponente verwenden? Auf jeden Fall könnten wir das schreiben:

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

Wir rufen den asynchronen Aktionsersteller auf, um die innere Funktion zu erhalten, die gerecht sein soll dispatch, und dann bestehen wir dispatch.

Dies ist jedoch noch umständlicher als die Originalversion! Warum sind wir überhaupt diesen Weg gegangen?

Wegen dem, was ich dir vorher gesagt habe. Wenn die Redux Thunk-Middleware aktiviert ist, ruft die Middleware jedes Mal, wenn Sie versuchen, eine Funktion anstelle eines Aktionsobjekts auszulösen, diese Funktion mit der dispatchMethode selbst als erstem Argument auf .

Also können wir dies stattdessen tun:

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

Schließlich sieht das Versenden einer asynchronen Aktion (wirklich eine Reihe von Aktionen) nicht anders aus als das Versenden einer einzelnen Aktion synchron zur Komponente. Das ist gut, weil es Komponenten egal sein sollte, ob etwas synchron oder asynchron passiert. Wir haben das einfach weg abstrahiert.

Da wir Redux „beigebracht“ haben, solche „speziellen“ Aktionsersteller zu erkennen (wir nennen sie Thunk- Aktionsersteller), können wir sie jetzt an jedem Ort verwenden, an dem wir reguläre Aktionsersteller verwenden würden. Zum Beispiel können wir sie verwenden mit 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)

Lesestatus in Thunks

Normalerweise enthalten Ihre Reduzierungen die Geschäftslogik zur Bestimmung des nächsten Status. Reduzierungen werden jedoch erst aktiviert, nachdem die Aktionen ausgelöst wurden. Was ist, wenn Sie einen Nebeneffekt (z. B. das Aufrufen einer API) in einem Thunk-Aktionsersteller haben und diesen unter bestimmten Bedingungen verhindern möchten?

Ohne die Thunk-Middleware würden Sie nur diese Überprüfung innerhalb der Komponente durchführen:

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

Beim Extrahieren eines Aktionserstellers ging es jedoch darum, diese sich wiederholende Logik über viele Komponenten hinweg zu zentralisieren. Glücklicherweise bietet Ihnen Redux Thunk eine Möglichkeit, den aktuellen Status des Redux-Stores zu lesen . Darüber hinaus dispatchwird es getStateals zweites Argument an die Funktion übergeben, die Sie von Ihrem Thunk-Aktionsersteller zurückgeben. Dadurch kann der Thunk den aktuellen Status des Geschäfts lesen.

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

Missbrauche dieses Muster nicht. Es ist gut, um API-Aufrufe zu beenden, wenn zwischengespeicherte Daten verfügbar sind, aber es ist keine sehr gute Grundlage, um auf Ihrer Geschäftslogik aufzubauen. Wenn Sie getState()nur verschiedene Aktionen bedingt auslösen, sollten Sie stattdessen die Geschäftslogik in die Reduzierungen einfügen.

Nächste Schritte

Nachdem Sie eine grundlegende Vorstellung davon haben, wie Thunks funktionieren, sehen Sie sich das asynchrone Redux- Beispiel an, in dem sie verwendet werden.

Möglicherweise finden Sie viele Beispiele, in denen Thunks Versprechen zurückgeben. Dies ist nicht erforderlich, kann aber sehr praktisch sein. Redux ist es egal, was Sie von einem Thunk zurückgeben, aber es gibt Ihnen den Rückgabewert von dispatch(). Aus diesem Grund können Sie ein Versprechen von einem Thunk zurückgeben und warten, bis es abgeschlossen ist, indem Sie anrufen dispatch(someThunkReturningPromise()).then(...).

Sie können komplexe Thunk-Action-Ersteller auch in mehrere kleinere Thunk-Action-Ersteller aufteilen. Die dispatchvon Thunks bereitgestellte Methode kann Thunks selbst akzeptieren, sodass Sie das Muster rekursiv anwenden können. Auch dies funktioniert am besten mit Promises, da Sie darüber hinaus einen asynchronen Steuerungsfluss implementieren können.

Bei einigen Apps befinden Sie sich möglicherweise in einer Situation, in der Ihre Anforderungen an den asynchronen Steuerungsfluss zu komplex sind, um mit Thunks ausgedrückt zu werden. Beispielsweise kann das Wiederholen fehlgeschlagener Anforderungen, der erneute Autorisierungsfluss mit Token oder ein schrittweises Onboarding zu ausführlich und fehleranfällig sein, wenn es auf diese Weise geschrieben wird. In diesem Fall sollten Sie sich erweiterte asynchrone Steuerungsflusslösungen wie Redux Saga oder Redux Loop ansehen . Bewerten Sie sie, vergleichen Sie die für Ihre Bedürfnisse relevanten Beispiele und wählen Sie das aus, das Ihnen am besten gefällt.

Verwenden Sie schließlich nichts (einschließlich Thunks), wenn Sie nicht wirklich das Bedürfnis danach haben. Denken Sie daran, dass Ihre Lösung je nach den Anforderungen so einfach aussehen kann wie

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

Schwitzen Sie nicht, es sei denn, Sie wissen, warum Sie das tun.


27
Asynchrone Aktionen scheinen eine so einfache und elegante Lösung für ein häufiges Problem zu sein. Warum wird die Unterstützung für sie nicht ohne Middleware in Redux integriert? Diese Antwort könnte dann viel prägnanter sein.
Phil Mander

83
@PhilMander Weil es viele alternative Muster wie github.com/raisemarketplace/redux-loop oder github.com/yelouafi/redux-saga gibt, die genauso (wenn nicht sogar eleganter) sind. Redux ist ein Low-Level-Tool. Sie können eine Obermenge erstellen, die Sie mögen, und sie separat verteilen.
Dan Abramov

16
Können Sie dies erklären: * Überlegen Sie, ob Sie die Geschäftslogik in die Reduzierungen einfügen möchten. * Bedeutet das, dass ich eine Aktion auslösen und dann im Reduzierer bestimmen sollte, welche weiteren Aktionen abhängig von meinem Status gesendet werden sollen? Meine Frage ist, versende ich dann andere Aktionen direkt in meinem Reduzierer und wenn nicht, woher versende ich sie dann?
Froginvasion

25
Dieser Satz gilt nur für den synchronen Fall. Wenn Sie beispielsweise schreiben if (cond) dispatch({ type: 'A' }) else dispatch({ type: 'B' }), sollten Sie dispatch({ type: 'C', something: cond })die Aktion in Reduzierungen möglicherweise einfach ignorieren action.somethingund stattdessen abhängig vom aktuellen Status ignorieren .
Dan Abramov

29
@DanAbramov Sie haben meine Gegenstimme nur dafür erhalten "Wenn Sie dieses Problem nicht haben, verwenden Sie das, was die Sprache bietet, und wählen Sie die einfachste Lösung." Erst danach wurde mir klar, wer es geschrieben hat!
Matt Lacey

189

Mit Redux-Saga

Wie Dan Abramov sagte, sollten Sie sich die Redux-Saga ansehen, wenn Sie eine erweiterte Kontrolle über Ihren Async-Code wünschen .

Diese Antwort ist ein einfaches Beispiel. Wenn Sie bessere Erklärungen dazu wünschen, warum Redux-Saga für Ihre Anwendung nützlich sein kann, überprüfen Sie diese andere Antwort .

Die allgemeine Idee ist, dass Redux-Saga einen ES6-Generator-Interpreter bietet, mit dem Sie einfach asynchronen Code schreiben können, der wie synchroner Code aussieht (aus diesem Grund finden Sie in Redux-Saga häufig unendliche while-Schleifen). Irgendwie baut die Redux-Saga ihre eigene Sprache direkt in Javascript auf. Die Redux-Saga kann sich zunächst etwas schwierig anfühlen, da Sie ein grundlegendes Verständnis der Generatoren benötigen, aber auch die Sprache der Redux-Saga verstehen müssen.

Ich werde hier versuchen, hier das Benachrichtigungssystem zu beschreiben, das ich auf der Redux-Saga aufgebaut habe. Dieses Beispiel läuft derzeit in der Produktion.

Erweiterte Benachrichtigungssystemspezifikation

  • Sie können die Anzeige einer Benachrichtigung anfordern
  • Sie können eine Benachrichtigung zum Ausblenden anfordern
  • Eine Benachrichtigung sollte nicht länger als 4 Sekunden angezeigt werden
  • Es können mehrere Benachrichtigungen gleichzeitig angezeigt werden
  • Es können nicht mehr als 3 Benachrichtigungen gleichzeitig angezeigt werden
  • Wenn eine Benachrichtigung angefordert wird, während bereits 3 Benachrichtigungen angezeigt werden, stellen Sie sie in die Warteschlange / verschieben Sie sie.

Ergebnis

Screenshot meiner Produktions-App Stample.co

Toast

Code

Hier habe ich die Benachrichtigung a genannt, toastaber dies ist ein Namensdetail.

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

Und der Reduzierer:

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

Verwendungszweck

Sie können einfach TOAST_DISPLAY_REQUESTEDEreignisse auslösen . Wenn Sie 4 Anfragen versenden, werden nur 3 Benachrichtigungen angezeigt, und die vierte wird etwas später angezeigt, sobald die erste Benachrichtigung verschwindet.

Beachten Sie, dass ich den Versand TOAST_DISPLAY_REQUESTEDvon JSX nicht ausdrücklich empfehle . Sie möchten lieber eine weitere Saga hinzufügen, die Ihre bereits vorhandenen App-Ereignisse abhört, und dann Folgendes versenden TOAST_DISPLAY_REQUESTED: Ihre Komponente, die die Benachrichtigung auslöst, muss nicht eng mit dem Benachrichtigungssystem verbunden sein.

Fazit

Mein Code ist nicht perfekt, läuft aber monatelang mit 0 Fehlern in der Produktion. Redux-Saga und Generatoren sind anfangs etwas schwierig, aber sobald Sie sie verstanden haben, ist diese Art von System ziemlich einfach zu bauen.

Es ist sogar recht einfach, komplexere Regeln zu implementieren, wie zum Beispiel:

  • Wenn zu viele Benachrichtigungen in die Warteschlange gestellt werden, geben Sie für jede Benachrichtigung weniger Anzeigezeit an, damit die Warteschlangengröße schneller abnimmt.
  • Erkennen Sie Änderungen der Fenstergröße und ändern Sie die maximale Anzahl der angezeigten Benachrichtigungen entsprechend (z. B. Desktop = 3, Telefonporträt = 2, Telefonlandschaft = 1).

Ehrlich gesagt, viel Glück beim richtigen Implementieren dieser Art von Dingen mit Thunks.

Beachten Sie, dass Sie mit Redux-Observable genau das Gleiche tun können, was der Redux-Saga sehr ähnlich ist. Es ist fast das gleiche und ist Geschmackssache zwischen Generatoren und RxJS.


18
Ich wünschte, Ihre Antwort wäre früher eingegangen, als die Frage gestellt wurde, da ich der Verwendung der Saga-Bibliothek für Nebenwirkungen für eine solche Geschäftslogik nicht mehr zustimmen kann. Reduzierer & Aktionsersteller sind für Zustandsübergänge. Workflows sind nicht mit Statusübergangsfunktionen identisch. Workflows durchlaufen Übergänge, sind jedoch selbst keine Übergänge. Redux + React fehlt dies von sich aus - genau deshalb ist Redux Saga so nützlich.
Atticus

4
Danke, ich versuche mein Bestes, um die Redux-Saga aus diesen Gründen populär zu machen :) Zu wenige Leute denken, dass die Redux-Saga derzeit nur ein Ersatz für Thunks ist und sehen nicht, wie die Redux-Saga komplexe und entkoppelte Workflows ermöglicht
Sebastien Lorber

1
Genau. Aktionen und Reduzierungen sind alle Teil der Zustandsmaschine. Manchmal benötigen Sie für komplexe Workflows etwas anderes, um die Zustandsmaschine zu orchestrieren, die nicht direkt Teil der Zustandsmaschine selbst ist!
Atticus

2
Aktionen: Nutzdaten / Ereignisse in den Übergangszustand. Reduzierstücke: Zustandsübergangsfunktionen. Komponenten: Benutzeroberflächen, die den Status widerspiegeln. Es fehlt jedoch ein wichtiger Punkt: Wie verwalten Sie den Prozess vieler Übergänge, die alle ihre eigene Logik haben und bestimmen, welcher Übergang als nächstes ausgeführt werden soll? Redux Saga!
Atticus

2
@ Mrbrdo Wenn Sie meine Antwort sorgfältig lesen, werden Sie feststellen, dass die Benachrichtigungs-Timeouts tatsächlich behandelt werden yield call(delay,timeoutValue);: Es ist nicht die gleiche API, aber es hat den gleichen Effekt
Sebastien Lorber

25

Ein Repository mit Beispielprojekten

Derzeit gibt es vier Beispielprojekte:

  1. Async-Code inline schreiben
  2. Async Action Creator extrahieren
  3. Verwenden Sie Redux Thunk
  4. Verwenden Sie Redux Saga

Die akzeptierte Antwort ist fantastisch.

Aber es fehlt etwas:

  1. Keine ausführbaren Beispielprojekte, nur einige Codefragmente.
  2. Kein Beispielcode für andere Alternativen wie:
    1. Redux Saga

Also habe ich das Hello Async- Repository erstellt, um die fehlenden Dinge hinzuzufügen:

  1. Ausführbare Projekte. Sie können sie ohne Änderungen herunterladen und ausführen.
  2. Geben Sie Beispielcode für weitere Alternativen an:

Redux Saga

Die akzeptierte Antwort enthält bereits Beispielcode-Snippets für Async Code Inline, Async Action Generator und Redux Thunk. Der Vollständigkeit halber stelle ich Code-Schnipsel für Redux Saga zur Verfügung:

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

Aktionen sind einfach und rein.

// component.js

import { connect } from 'react-redux'

// ...

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

// ...

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

Mit Komponenten ist nichts Besonderes.

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

Sagas basieren auf ES6-Generatoren

// index.js

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

const sagaMiddleware = createSagaMiddleware()

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

sagaMiddleware.run(saga)

Im Vergleich zu Redux Thunk

Vorteile

  • Sie landen nicht in der Rückrufhölle.
  • Sie können Ihre asynchronen Flows einfach testen.
  • Deine Handlungen bleiben rein.

Nachteile

  • Es hängt von ES6-Generatoren ab, was relativ neu ist.

Bitte beziehen Sie sich auf das ausführbare Projekt, wenn die obigen Codefragmente nicht alle Ihre Fragen beantworten.


23

Sie können dies mit Redux-Thunk tun . Im Redux-Dokument gibt es eine Anleitung für asynchrone Aktionen wie setTimeout.


Nur eine kurze Folgefrage: Wenn Sie Middleware verwenden applyMiddleware(ReduxPromise, thunk)(createStore), fügen Sie auf diese Weise mehrere Middleware hinzu (durch Koma getrennt?), Da ich anscheinend keinen Thunk zum Laufen bringen kann.
Ilja

1
@Ilja Dies sollte funktionieren:const store = createStore(reducer, applyMiddleware([ReduxPromise, thunk]));
Geniuscarrier

22

Ich würde empfehlen, auch das SAM-Muster zu betrachten .

Das SAM-Muster befürwortet die Aufnahme eines "Prädikats für die nächste Aktion", bei dem (automatische) Aktionen wie "Benachrichtigungen verschwinden automatisch nach 5 Sekunden" ausgelöst werden, sobald das Modell aktualisiert wurde (SAM-Modell ~ Reduzierungsstatus + Speicher).

Das Muster befürwortet die Sequenzierung von Aktionen und Modellmutationen nacheinander, da der "Kontrollstatus" des Modells "steuert", welche Aktionen vom Prädikat für die nächste Aktion aktiviert und / oder automatisch ausgeführt werden. Sie können einfach (im Allgemeinen) nicht vorhersagen, in welchem ​​Zustand sich das System befindet, bevor eine Aktion verarbeitet wird, und daher, ob Ihre nächste erwartete Aktion zulässig / möglich ist.

So zum Beispiel der Code,

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

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

wäre mit SAM nicht zulässig, da die Tatsache, dass eine hideNotification-Aktion ausgelöst werden kann, davon abhängt, dass das Modell den Wert "showNotication: true" erfolgreich akzeptiert. Es könnte andere Teile des Modells geben, die verhindern, dass es akzeptiert wird, und daher gibt es keinen Grund, die Aktion hideNotification auszulösen.

Ich würde dringend empfehlen, ein korrektes Prädikat für die nächste Aktion zu implementieren, nachdem die Speicheraktualisierungen und der neue Steuerungsstatus des Modells bekannt sind. Dies ist der sicherste Weg, um das gesuchte Verhalten zu implementieren.

Sie können sich uns auf Gitter anschließen, wenn Sie möchten. Hier finden Sie auch eine SAM-Anleitung für die ersten Schritte .


Ich habe bisher nur die Oberfläche zerkratzt, bin aber bereits vom SAM-Muster begeistert. V = S( vm( M.present( A(data) ) ), nap(M))ist einfach wunderschön. Vielen Dank für Ihre Gedanken und Erfahrungen. Ich werde tiefer graben.

@ftor, danke! Als ich es das erste Mal schrieb, hatte ich das gleiche Gefühl. Ich verwende SAM seit fast einem Jahr in der Produktion und kann mir keine Zeit vorstellen, in der ich das Gefühl hatte, eine Bibliothek zur Implementierung von SAM zu benötigen (sogar vdom, obwohl ich sehen kann, wann es verwendet werden kann). Nur eine Codezeile, das war's! SAM erzeugt isomorphen Code, es gibt keine Unklarheit darüber, wie man mit asynchronen Aufrufen umgeht ... Ich kann mir keine Zeit vorstellen, in der ich dachte, was mache ich?
Metaprogrammer

SAM ist ein echtes Software Engineering-Muster (hat gerade ein Alexa SDK damit erstellt). Es basiert auf TLA + und versucht, jedem Entwickler die Kraft dieser unglaublichen Arbeit zu bieten. SAM korrigiert drei Näherungen, die (so ziemlich) jeder seit Jahrzehnten verwendet: - Aktionen können den Anwendungsstatus manipulieren - Zuweisungen entsprechen einer Mutation - es gibt keine genaue Definition, was ein Programmierschritt ist (z. B. ist a = b * ca Schritt , sind 1 / lesen b, c 2 / berechnen b * c, 3 / weisen a mit dem Ergebnis drei verschiedene Schritte zu?
Metaprogrammer

20

Nachdem ich die verschiedenen gängigen Ansätze ausprobiert hatte (Action-Ersteller, Thunks, Sagen, Epen, Effekte, benutzerdefinierte Middleware), hatte ich immer noch das Gefühl, dass möglicherweise Verbesserungspotenzial besteht, und dokumentierte meine Reise in diesem Blog-Artikel: Wo stelle ich meine Geschäftslogik ein? eine React / Redux-Anwendung?

Ähnlich wie bei den Diskussionen hier habe ich versucht, die verschiedenen Ansätze gegenüberzustellen und zu vergleichen. Schließlich führte es mich zur Einführung einer neuen Bibliotheksredux -Logik, die sich von Epen, Sagen und benutzerdefinierter Middleware inspirieren lässt.

Sie können damit Aktionen abfangen, um sie zu validieren, zu überprüfen, zu autorisieren und asynchrone E / A-Vorgänge auszuführen.

Einige allgemeine Funktionen können einfach deklariert werden, z. B. Entprellen, Drosseln, Abbrechen und nur mithilfe der Antwort der letzten Anforderung (takeLatest). Redux-Logik umschließt Ihren Code und bietet diese Funktionalität für Sie.

So können Sie Ihre Kerngeschäftslogik nach Belieben implementieren. Sie müssen keine Observablen oder Generatoren verwenden, es sei denn, Sie möchten. Verwenden Sie Funktionen und Rückrufe, Versprechen, asynchrone Funktionen (async / await) usw.

Der Code für eine einfache 5s-Benachrichtigung lautet wie folgt:

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

Ich habe ein erweitertes Benachrichtigungsbeispiel in meinem Repo, das ähnlich wie das von Sebastian Lorber beschriebene funktioniert, bei dem Sie die Anzeige auf N Elemente beschränken und durch alle Elemente in der Warteschlange drehen können. Beispiel für eine Redux-Logik-Benachrichtigung

Ich habe eine Vielzahl von Redux-Logik-Jsfiddle-Live-Beispielen sowie vollständige Beispiele . Ich arbeite weiterhin an Dokumenten und Beispielen.

Ich würde gerne Ihr Feedback hören.


Ich bin mir nicht sicher, ob ich Ihre Bibliothek mag, aber ich mag Ihren Artikel! Gut gemacht, Mann! Sie haben genug Arbeit geleistet, um anderen Zeit zu sparen.
Tyler Long

2
Ich habe hier ein Beispielprojekt für Redux-Logik erstellt: github.com/tylerlong/hello-async/tree/master/redux-logic Ich denke, es ist eine gut gestaltete Software, und ich sehe keine größeren Nachteile im Vergleich zu anderen Alternativen.
Tyler Long

9

Ich verstehe, dass diese Frage etwas alt ist, aber ich werde eine andere Lösung mit Redux-Observable aka vorstellen . Epos.

Zitieren der offiziellen Dokumentation:

Was ist Redux-beobachtbar?

RxJS 5-basierte Middleware für Redux. Erstellen und brechen Sie asynchrone Aktionen ab, um Nebenwirkungen und mehr zu erzielen.

Ein Epos ist das Kernprimitiv von Redux-Observable.

Es ist eine Funktion, die einen Strom von Aktionen ausführt und einen Strom von Aktionen zurückgibt. Aktionen rein, Aktionen raus.

Mit mehr oder weniger Worten können Sie eine Funktion erstellen, die Aktionen über einen Stream empfängt und dann einen neuen Stream von Aktionen zurückgibt (unter Verwendung allgemeiner Nebenwirkungen wie Zeitüberschreitungen, Verzögerungen, Intervalle und Anforderungen).

Lassen Sie mich den Code posten und dann etwas mehr darüber erklären

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)

Der Schlüsselcode zur Lösung dieses Problems ist so einfach wie Sie sehen können. Das einzige, was sich von den anderen Antworten unterscheidet, ist die Funktion rootEpic.

Punkt 1. Wie bei Sagen müssen Sie die Epen kombinieren, um eine Funktion der obersten Ebene zu erhalten, die einen Aktionsstrom empfängt und einen Aktionsstrom zurückgibt, sodass Sie sie mit der Middleware-Factory createEpicMiddleware verwenden können . In unserem Fall brauchen wir nur einen, also haben wir nur unser rootEpic, damit wir nichts kombinieren müssen, aber es ist eine gute Tatsache.

Punkt 2. Unser rootEpic, das sich um die Logik der Nebenwirkungen kümmert, benötigt nur etwa 5 Codezeilen, was großartig ist! Einschließlich der Tatsache, dass ziemlich deklarativ ist!

Punkt 3. Zeile für Zeile rootEpic Erklärung (in Kommentaren)

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

Ich hoffe, es hilft!


Können Sie erklären, was die spezifischen API-Methoden hier tun, wie z switchMap.
Dmitri Zaitsev

1
Wir verwenden Redux-Observable in unserer React Native-App unter Windows. Es ist eine elegante Implementierungslösung für ein komplexes, hoch asynchrones Problem und bietet fantastische Unterstützung über die Probleme mit dem Gitterkanal und GitHub. Die zusätzliche Komplexität lohnt sich nur, wenn Sie genau das Problem finden, das es natürlich lösen soll.
Matt Hargett

8

Warum sollte es so schwer sein? Es ist nur UI-Logik. Verwenden Sie eine spezielle Aktion, um Benachrichtigungsdaten festzulegen:

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

und eine dedizierte Komponente zur Anzeige:

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

In diesem Fall sollten die Fragen lauten: "Wie bereinigen Sie den alten Status?", "Wie benachrichtige ich eine Komponente, wenn sich die Zeit geändert hat?"

Sie können eine TIMEOUT-Aktion implementieren, die bei setTimeout von einer Komponente ausgelöst wird.

Vielleicht ist es in Ordnung, es zu reinigen, wenn eine neue Benachrichtigung angezeigt wird.

Wie auch immer, setTimeoutirgendwo sollte es welche geben , oder? Warum nicht in einer Komponente?

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

Die Motivation ist, dass die Funktion zum Ausblenden von Benachrichtigungen wirklich ein Problem für die Benutzeroberfläche darstellt. Dies vereinfacht das Testen Ihrer Geschäftslogik.

Es scheint nicht sinnvoll zu sein, zu testen, wie es implementiert ist. Es ist nur sinnvoll zu überprüfen, wann die Benachrichtigung abläuft. Also weniger Code zum Stub, schnellere Tests, sauberer Code.


1
Dies sollte die beste Antwort sein.
mmla

6

Wenn Sie bei ausgewählten Aktionen eine Timeout-Behandlung wünschen, können Sie den Middleware- Ansatz ausprobieren . Ich hatte ein ähnliches Problem bei der selektiven Behandlung versprechungsbasierter Aktionen, und diese Lösung war flexibler.

Nehmen wir an, Ihr Action-Ersteller sieht folgendermaßen aus:

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

Das Zeitlimit kann in der obigen Aktion mehrere Werte enthalten

  • Zahl in ms - für eine bestimmte Zeitüberschreitungsdauer
  • true - für eine konstante Timeout-Dauer. (in der Middleware behandelt)
  • undefiniert - zum sofortigen Versand

Ihre Middleware-Implementierung würde folgendermaßen aussehen:

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

Sie können jetzt alle Ihre Aktionen mithilfe von Redux durch diese Middleware-Ebene leiten.

createStore(reducer, applyMiddleware(timeoutMiddleware))

Ähnliche Beispiele finden Sie hier


5

Der geeignete Weg, dies zu tun, ist die Verwendung von Redux Thunk , einer für Middlex beliebten Middleware gemäß der Redux Thunk-Dokumentation:

"Mit der Redux Thunk-Middleware können Sie Aktionsersteller schreiben, die eine Funktion anstelle einer Aktion zurückgeben. Der Thunk kann verwendet werden, um den Versand einer Aktion zu verzögern oder nur, wenn eine bestimmte Bedingung erfüllt ist. Die innere Funktion empfängt die Speichermethoden dispatch und getState als Parameter ".

Im Grunde gibt es eine Funktion zurück, und Sie können Ihren Versand verzögern oder in einen Zustandszustand versetzen.

So etwas wird den Job für Sie erledigen:

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

Es ist einfach. Verwenden Sie das Trim-Redux- Paket und schreiben Sie so an componentDidMountoder an eine andere Stelle und töten Sie es componentWillUnmount.

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

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

3

Redux selbst ist eine ziemlich ausführliche Bibliothek, und für solche Dinge müssten Sie etwas wie Redux-thunk verwenden , das eine dispatchFunktion gibt, damit Sie das Schließen der Benachrichtigung nach einigen Sekunden versenden können.

Ich habe eine Bibliothek erstellt , um Probleme wie Ausführlichkeit und Kompositionsfähigkeit zu beheben. Ihr Beispiel sieht folgendermaßen aus:

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

Daher erstellen wir Synchronisierungsaktionen, um Benachrichtigungen innerhalb einer asynchronen Aktion anzuzeigen, die Informationen zum Hintergrund anfordern oder später überprüfen können, ob die Benachrichtigung manuell geschlossen wurde.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.