Warum benötigen wir Middleware für den asynchronen Ablauf in Redux?


684

Laut den Dokumenten, „ohne Middleware, Redux Speicher unterstützen nur synchronen Datenfluss“ . Ich verstehe nicht, warum das so ist. Warum kann die Containerkomponente nicht die asynchrone API und dann dispatchdie Aktionen aufrufen ?

Stellen Sie sich zum Beispiel eine einfache Benutzeroberfläche vor: ein Feld und eine Schaltfläche. Wenn der Benutzer die Taste drückt, wird das Feld mit Daten von einem Remote-Server gefüllt.

Ein Feld und eine Schaltfläche

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

Wenn die exportierte Komponente gerendert wird, kann ich auf die Schaltfläche klicken und die Eingabe wird korrekt aktualisiert.

Beachten Sie die updateFunktion in derconnect Aufruf. Es löst eine Aktion aus, die der App mitteilt, dass sie aktualisiert wird, und führt dann einen asynchronen Aufruf aus. Nach Beendigung des Aufrufs wird der angegebene Wert als Nutzlast einer anderen Aktion gesendet.

Was ist falsch an diesem Ansatz? Warum sollte ich Redux Thunk oder Redux Promise verwenden, wie in der Dokumentation vorgeschlagen?

BEARBEITEN: Ich habe das Redux-Repo nach Hinweisen durchsucht und festgestellt, dass Action Creators in der Vergangenheit reine Funktionen sein mussten. Hier ist beispielsweise ein Benutzer, der versucht, eine bessere Erklärung für den asynchronen Datenfluss bereitzustellen:

Der Aktionsersteller selbst ist immer noch eine reine Funktion, aber die zurückgegebene Thunk-Funktion muss es nicht sein, und er kann unsere asynchronen Aufrufe ausführen

Action-Ersteller müssen nicht mehr rein sein. Thunk / Promise-Middleware war in der Vergangenheit definitiv erforderlich, aber es scheint, dass dies nicht mehr der Fall ist?


53
Action Creators mussten niemals reine Funktionen sein. Es war ein Fehler in den Dokumenten, keine Entscheidung, die sich geändert hat.
Dan Abramov

1
@ DanAbramov für die Testbarkeit kann es jedoch eine gute Übung sein. Redux-Saga erlaubt dies: stackoverflow.com/a/34623840/82609
Sebastien Lorber

Antworten:


697

Was ist falsch an diesem Ansatz? Warum sollte ich Redux Thunk oder Redux Promise verwenden, wie in der Dokumentation vorgeschlagen?

An diesem Ansatz ist nichts auszusetzen. In einer großen Anwendung ist dies nur unpraktisch, da verschiedene Komponenten dieselben Aktionen ausführen. Möglicherweise möchten Sie einige Aktionen entprellen oder einen lokalen Status wie das automatische Inkrementieren von IDs in der Nähe von Aktionserstellern usw. beibehalten. Dies ist also einfacher die Wartungsperspektive, um Aktionsersteller in separate Funktionen zu extrahieren.

Sie können meine Antwort auf "So senden Sie eine Redux-Aktion mit einer Zeitüberschreitung" lesen, um eine detailliertere Anleitung zu erhalten.

Middleware wie Redux Thunk oder Redux Versprechen gibt Ihnen nur „Syntax Zucker“ für Thunks oder Versprechen Disposition, aber Sie nicht haben zu verwenden.

Ohne Middleware könnte Ihr Action Creator also so aussehen

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

Aber mit Thunk Middleware können Sie es so schreiben:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

Es gibt also keinen großen Unterschied. Eine Sache, die ich an letzterem Ansatz mag, ist, dass es der Komponente egal ist, dass der Aktionsersteller asynchron ist. Es wird nur dispatchnormal aufgerufen, es kann auch verwendet werden mapDispatchToProps, um einen solchen Aktionsersteller mit einer kurzen Syntax usw. zu binden. Die Komponenten wissen nicht, wie Aktionsersteller implementiert sind, und Sie können zwischen verschiedenen asynchronen Ansätzen (Redux Thunk, Redux Promise, Redux Saga) wechseln ) ohne die Komponenten zu wechseln. Auf der anderen Seite wissen Ihre Komponenten mit dem früheren expliziten Ansatz genau, dass ein bestimmter Aufruf asynchron ist und benötigt wirddispatch von einer Konvention übergeben werden muss (z. B. als Synchronisierungsparameter).

Denken Sie auch darüber nach, wie sich dieser Code ändern wird. Angenommen, wir möchten eine zweite Funktion zum Laden von Daten haben und diese in einem einzigen Aktionsersteller kombinieren.

Beim ersten Ansatz müssen wir uns bewusst sein, welche Art von Aktionsersteller wir nennen:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Mit Redux Thunk können Aktionsersteller dispatchdas Ergebnis anderer Aktionsersteller sein und nicht einmal darüber nachdenken, ob diese synchron oder asynchron sind:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

Wenn Sie später möchten, dass Ihre Aktionsersteller den aktuellen Redux-Status getStateanzeigen , können Sie bei diesem Ansatz einfach das zweite Argument verwenden, das an die Thunks übergeben wird, ohne den aufrufenden Code zu ändern:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

Wenn Sie es ändern müssen, um synchron zu sein, können Sie dies auch tun, ohne einen aufrufenden Code zu ändern:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

Der Vorteil der Verwendung von Middleware wie Redux Thunk oder Redux Promise besteht darin, dass Komponenten nicht wissen, wie Aktionsersteller implementiert sind, ob ihnen der Redux-Status wichtig ist, ob sie synchron oder asynchron sind und ob sie andere Aktionsersteller aufrufen oder nicht . Der Nachteil ist ein bisschen Indirektion, aber wir glauben, dass es sich in realen Anwendungen lohnt.

Schließlich ist Redux Thunk und Freunde nur ein möglicher Ansatz für asynchrone Anforderungen in Redux-Apps. Ein weiterer interessanter Ansatz ist Redux Saga, mit dem Sie lang laufende Dämonen („Sagas“) definieren können, die Aktionen sofort ausführen und Anforderungen transformieren oder ausführen, bevor Sie Aktionen ausgeben. Dies verschiebt die Logik von Aktionserstellern in Sagen. Vielleicht möchten Sie es ausprobieren und später auswählen, was am besten zu Ihnen passt.

Ich suchte im Redux-Repo nach Hinweisen und stellte fest, dass Action Creators in der Vergangenheit reine Funktionen sein mussten.

Das ist falsch. Die Dokumente sagten dies, aber die Dokumente waren falsch.
Action Creators mussten niemals reine Funktionen sein.
Wir haben die Dokumente korrigiert, um dies widerzuspiegeln.


57
Kurz gesagt, Dans Gedanke lautet: Middleware ist ein zentraler Ansatz. Auf diese Weise können Sie Ihre Komponenten einfacher und allgemeiner halten und den Datenfluss an einem Ort steuern. Wenn Sie große App pflegen, werden Sie es genießen =)
Sergey Lapin

3
@asdfasdfads Ich verstehe nicht, warum es nicht funktionieren würde. Es würde genauso funktionieren; setzt alertnach dispatch()der Aktion ing.
Dan Abramov

9
Vorletzte Zeile in Ihrem ersten Codebeispiel : loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch. Warum muss ich den Versand übergeben? Wenn es konventionell immer nur einen einzigen globalen Speicher gibt, warum verweise ich store.dispatchdann nicht einfach direkt darauf und wann immer ich muss, z. B. in loadData?
Søren Debois

10
@ SørenDebois Wenn Ihre App nur clientseitig ist, würde das funktionieren. Wenn es auf dem Server gerendert wird, möchten Sie storefür jede Anforderung eine andere Instanz haben, damit Sie sie nicht vorher definieren können.
Dan Abramov

3
Ich möchte nur darauf hinweisen, dass diese Antwort 139 Zeilen enthält, was 9,92-mal mehr ist als der Quellcode von Redux-Thunk, der aus 14 Zeilen besteht: github.com/gaearon/redux-thunk/blob/master/src/index.js
Guy

447

Das tust du nicht.

Aber ... du solltest Redux-Saga verwenden :)

Dan Abramovs Antwort ist richtig, redux-thunkaber ich werde ein bisschen mehr über Redux-Saga sprechen , die ziemlich ähnlich, aber mächtiger ist.

Imperativ VS deklarativ

  • DOM : jQuery ist zwingend erforderlich / React ist deklarativ
  • Monaden : IO ist zwingend / Free ist deklarativ
  • Redux-Effekte : redux-thunksind zwingend / redux-sagadeklarativ

Wenn Sie einen Thunk in Ihren Händen haben, wie eine IO-Monade oder ein Versprechen, können Sie nicht leicht wissen, was es tun wird, wenn Sie es ausführen. Die einzige Möglichkeit, einen Thunk zu testen, besteht darin, ihn auszuführen und den Dispatcher (oder die gesamte Außenwelt, wenn er mit mehr Dingen interagiert ...) zu verspotten.

Wenn Sie Mocks verwenden, führen Sie keine funktionale Programmierung durch.

Durch die Linse der Nebenwirkungen gesehen, sind Mocks eine Flagge, dass Ihr Code unrein ist, und im Auge des funktionalen Programmierers ein Beweis dafür, dass etwas nicht stimmt. Anstatt eine Bibliothek herunterzuladen, um zu überprüfen, ob der Eisberg intakt ist, sollten wir um ihn herum segeln. Ein Hardcore-TDD / Java-Typ hat mich einmal gefragt, wie man sich in Clojure lustig macht. Die Antwort lautet: Normalerweise nicht. Wir sehen es normalerweise als Zeichen, dass wir unseren Code umgestalten müssen.

Quelle

Die Sagen (wie sie in implementiert wurden redux-saga) sind deklarativ und wie die Komponenten Free Monad oder React viel einfacher zu testen, ohne sich zu verspotten.

Siehe auch diesen Artikel :

In modernen FP sollten wir keine Programme schreiben - wir sollten Beschreibungen von Programmen schreiben, die wir dann nach Belieben überprüfen, transformieren und interpretieren können.

(Eigentlich ist die Redux-Saga wie ein Hybrid: Der Fluss ist zwingend, aber die Auswirkungen sind deklarativ.)

Verwirrung: Aktionen / Ereignisse / Befehle ...

In der Frontend-Welt herrscht große Verwirrung darüber, wie einige Backend-Konzepte wie CQRS / EventSourcing und Flux / Redux zusammenhängen können, vor allem, weil wir in Flux den Begriff "Aktion" verwenden, der manchmal sowohl imperativen Code ( LOAD_USER) als auch Ereignisse () darstellen kann USER_LOADED). Ich glaube, dass Sie wie beim Event-Sourcing nur Events versenden sollten.

Verwenden von Sagen in der Praxis

Stellen Sie sich eine App mit einem Link zu einem Benutzerprofil vor. Der idiomatische Weg, dies mit jeder Middleware zu handhaben, wäre:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

Diese Saga übersetzt zu:

Rufen Sie jedes Mal, wenn auf einen Benutzernamen geklickt wird, das Benutzerprofil ab und senden Sie dann ein Ereignis mit dem geladenen Profil aus.

Wie Sie sehen können, gibt es einige Vorteile von redux-saga.

Die Verwendung von takeLatest Genehmigungen, um auszudrücken, dass Sie nur daran interessiert sind, die Daten des letzten angeklickten Benutzernamens zu erhalten (behandeln Sie Parallelitätsprobleme, falls der Benutzer sehr schnell auf viele Benutzernamen klickt). Diese Art von Sachen ist schwer mit Thunks. Sie hätten verwenden können, takeEverywenn Sie dieses Verhalten nicht möchten.

Sie halten Action-Schöpfer rein. Beachten Sie, dass es immer noch nützlich ist, actionCreators (in Sagen putund Komponenten) beizubehaltendispatch ) , da dies Ihnen möglicherweise helfen wird, die Aktionsvalidierung (Assertions / Flow / Typoskript) in Zukunft hinzuzufügen.

Ihr Code wird viel testbarer, da die Auswirkungen deklarativ sind

Sie müssen keine rpc-ähnlichen Aufrufe mehr auslösen actions.loadUser(). Ihre Benutzeroberfläche muss nur das versenden, was geschehen ist. Wir feuern nur noch Ereignisse ab (immer in der Vergangenheitsform!) Und keine Aktionen mehr. Dies bedeutet, dass Sie entkoppelte "Enten" oder begrenzte Kontexte erstellen können und dass die Saga als Kopplungspunkt zwischen diesen modularen Komponenten fungieren kann.

Dies bedeutet, dass Ihre Ansichten einfacher zu verwalten sind, da sie diese Übersetzungsebene zwischen dem, was passiert ist und dem, was als Effekt passieren sollte, nicht mehr enthalten müssen

Stellen Sie sich zum Beispiel eine unendliche Bildlaufansicht vor. CONTAINER_SCROLLEDKann führen zuNEXT_PAGE_LOADED , aber liegt es wirklich in der Verantwortung des scrollbaren Containers, zu entscheiden, ob wir eine andere Seite laden sollen oder nicht? Dann muss er sich komplizierterer Dinge bewusst sein, z. B. ob die letzte Seite erfolgreich geladen wurde oder ob bereits eine Seite geladen werden soll oder ob keine weiteren Elemente mehr geladen werden müssen. Ich denke nicht: Für maximale Wiederverwendbarkeit sollte der scrollbare Container nur beschreiben, dass er gescrollt wurde. Das Laden einer Seite ist ein "Geschäftseffekt" dieser Schriftrolle

Einige mögen argumentieren, dass Generatoren den Status außerhalb des Redux-Speichers mit lokalen Variablen von Natur aus verbergen können, aber wenn Sie anfangen, komplexe Dinge innerhalb von Thunks durch Starten von Timern usw. zu orchestrieren, hätten Sie sowieso das gleiche Problem. Und es gibt einen selectEffekt, der es jetzt ermöglicht, einen Status aus Ihrem Redux-Shop abzurufen.

Sagas können zeitlich begrenzt sein und ermöglichen auch komplexe Flow-Logging- und Dev-Tools, an denen derzeit gearbeitet wird. Hier ist eine einfache asynchrone Flussprotokollierung, die bereits implementiert ist:

Saga Flow Logging

Entkopplung

Sagas ersetzen nicht nur Redux-Thunks. Sie stammen aus dem Backend / verteilten Systemen / Event-Sourcing.

Es ist ein weit verbreitetes Missverständnis, dass Sagen nur dazu da sind, Ihre Redux-Thunks durch bessere Testbarkeit zu ersetzen. Eigentlich ist dies nur ein Implementierungsdetail der Redux-Saga. Die Verwendung deklarativer Effekte ist aus Gründen der Testbarkeit besser als Thunks, aber das Saga-Muster kann zusätzlich zu imperativem oder deklarativem Code implementiert werden.

Erstens ist die Saga eine Software, die es ermöglicht, lang laufende Transaktionen (eventuelle Konsistenz) und Transaktionen über verschiedene begrenzte Kontexte hinweg (domänengesteuerte Designsprache) zu koordinieren.

Stellen Sie sich vor, es gibt Widget1 und Widget2, um dies für die Frontend-Welt zu vereinfachen. Wenn auf eine Schaltfläche in Widget1 geklickt wird, sollte dies Auswirkungen auf Widget2 haben. Anstatt die beiden Widgets miteinander zu koppeln (dh Widget1 löst eine Aktion aus, die auf Widget2 abzielt), sendet Widget1 nur, dass auf die Schaltfläche geklickt wurde. Dann wartet die Saga auf diese Schaltfläche, klickt und aktualisiert dann Widget2, indem ein neues Ereignis entfernt wird, das Widget2 kennt.

Dies fügt eine Indirektionsebene hinzu, die für einfache Apps nicht erforderlich ist, die Skalierung komplexer Anwendungen jedoch einfacher macht. Sie können jetzt Widget1 und Widget2 in verschiedenen npm-Repositorys veröffentlichen, sodass sie sich nie gegenseitig kennen müssen, ohne dass sie eine globale Aktionsregistrierung gemeinsam nutzen müssen. Die 2 Widgets sind jetzt begrenzte Kontexte, die separat leben können. Sie müssen nicht konsistent sein und können auch in anderen Apps wiederverwendet werden. Die Saga ist der Kopplungspunkt zwischen den beiden Widgets, die sie auf sinnvolle Weise für Ihr Unternehmen koordinieren.

Einige nette Artikel zur Strukturierung Ihrer Redux-App, in denen Sie die Redux-Saga aus Gründen der Entkopplung verwenden können:

Ein konkreter Anwendungsfall: Benachrichtigungssystem

Ich möchte, dass meine Komponenten die Anzeige von In-App-Benachrichtigungen auslösen können. Ich möchte jedoch nicht, dass meine Komponenten in hohem Maße an das Benachrichtigungssystem gekoppelt sind, das über eigene Geschäftsregeln verfügt (maximal 3 Benachrichtigungen werden gleichzeitig angezeigt, Benachrichtigungswarteschlange, 4 Sekunden Anzeigezeit usw.).

Ich möchte nicht, dass meine JSX-Komponenten entscheiden, wann eine Benachrichtigung angezeigt / ausgeblendet wird. Ich gebe ihm nur die Möglichkeit, eine Benachrichtigung anzufordern und die komplexen Regeln in der Saga zu belassen. Diese Art von Sachen ist mit Thunks oder Versprechungen ziemlich schwer umzusetzen.

Benachrichtigungen

Ich habe hier beschrieben , wie dies mit Saga gemacht werden kann

Warum heißt es Saga?

Der Begriff Saga stammt aus der Backend-Welt. Ich habe Yassine (den Autor der Redux-Saga) in einer langen Diskussion zunächst mit diesem Begriff bekannt gemacht .

Ursprünglich wurde dieser Begriff mit einem Papier eingeführt . Das Saga-Muster sollte verwendet werden, um eventuelle Konsistenz bei verteilten Transaktionen zu behandeln. Die Verwendung wurde jedoch von Backend-Entwicklern auf eine breitere Definition erweitert, sodass es jetzt auch den "Prozessmanager" abdeckt. Muster (irgendwie ist das ursprüngliche Saga-Muster eine spezielle Form des Prozessmanagers).

Heute ist der Begriff "Saga" verwirrend, da er zwei verschiedene Dinge beschreiben kann. Da es in der Redux-Saga verwendet wird, beschreibt es keine Möglichkeit, verteilte Transaktionen zu verarbeiten, sondern eine Möglichkeit, Aktionen in Ihrer App zu koordinieren. redux-sagahätte auch angerufen werden können redux-process-manager.

Siehe auch:

Alternativen

Wenn Sie die Idee der Verwendung von Generatoren nicht mögen, sich aber für das Saga-Muster und seine Entkopplungseigenschaften interessieren, können Sie dasselbe auch mit redux-beobachtbar erreichen , das den Namen verwendet epic, um genau dasselbe Muster zu beschreiben, jedoch mit RxJS. Wenn Sie bereits mit Rx vertraut sind, werden Sie sich wie zu Hause fühlen.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

Einige nützliche Ressourcen der Redux-Saga

2017 rät

  • Überbeanspruchen Sie die Redux-Saga nicht nur, um sie zu verwenden. Nur testbare API-Aufrufe sind es nicht wert.
  • Entfernen Sie in den einfachsten Fällen keine Thunks aus Ihrem Projekt.
  • Zögern Sie nicht, Thunks einzusenden, yield put(someActionThunk)wenn es Sinn macht.

Wenn Sie Angst haben, Redux-Saga (oder Redux-Observable) zu verwenden, aber nur das Entkopplungsmuster benötigen, überprüfen Sie Redux-Dispatch-Subscribe : Es ermöglicht das Abhören von Sendungen und das Auslösen neuer Sendungen im Listener.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});

64
Dies wird jedes Mal besser, wenn ich wiederkomme. Erwägen Sie, daraus einen Blog-Beitrag zu machen :).
RainerAtSpirit

4
Vielen Dank für ein gutes Schreiben. In bestimmten Aspekten bin ich mir jedoch nicht einig. Wie ist LOAD_USER zwingend erforderlich? Für mich ist es nicht nur deklarativ, sondern bietet auch gut lesbaren Code. Wie z. "Wenn ich diese Taste drücke, möchte ich ADD_ITEM". Ich kann mir den Code ansehen und genau verstehen, was los ist. Wenn es stattdessen etwas mit dem Effekt "BUTTON_CLICK" genannt würde, müsste ich das nachschlagen.
Swelet

4
Gute Antwort. Es gibt jetzt eine andere Alternative: github.com/blesh/redux-observable
swennemen

4
@swelet Entschuldigung für die späte Antwort. Wenn Sie versenden ADD_ITEM, ist dies unbedingt erforderlich, da Sie eine Aktion versenden, die sich auf Ihr Geschäft auswirken soll: Sie erwarten, dass die Aktion etwas bewirkt. Als deklarativ bezeichnen Sie die Philosophie des Event-Sourcing: Sie senden keine Aktionen aus, um Änderungen an Ihren Anwendungen auszulösen, sondern Sie senden vergangene Ereignisse aus, um zu beschreiben, was in Ihrer Anwendung passiert ist. Der Versand eines Ereignisses sollte ausreichen, um zu berücksichtigen, dass sich der Status der Anwendung geändert hat. Die Tatsache, dass es einen Redux-Store gibt, der auf Ereignisse reagiert, ist ein optionales Implementierungsdetail
Sebastien Lorber

3
Ich mag diese Antwort nicht, weil sie von der eigentlichen Frage ablenkt, um jemandes eigene Bibliothek zu vermarkten. Diese Antwort bietet einen Vergleich der beiden Bibliotheken, was nicht die Absicht der Frage war. Die eigentliche Frage ist, ob überhaupt Middleware verwendet werden soll, was durch die akzeptierte Antwort erklärt wird.
Abhinav Manchanda

31

Die kurze Antwort : scheint mir ein völlig vernünftiger Ansatz für das Asynchronitätsproblem zu sein. Mit ein paar Einschränkungen.

Ich hatte einen sehr ähnlichen Gedankengang, als ich an einem neuen Projekt arbeitete, das wir gerade bei meiner Arbeit begonnen hatten. Ich war ein großer Fan von Vanilla Redux 'elegantem System zur Aktualisierung des Geschäfts und zum erneuten Rendern von Komponenten auf eine Weise, die sich aus den Eingeweiden eines React-Komponentenbaums heraushält. Es kam mir komisch vor, mich in diesen eleganten dispatchMechanismus zu integrieren, um mit Asynchronität umzugehen.

Am Ende hatte ich einen wirklich ähnlichen Ansatz wie in einer Bibliothek, die ich aus unserem Projekt herausgerechnet habe, das wir React-Redux-Controller nannten .

Ich bin aus mehreren Gründen nicht mit dem genauen Ansatz vorgegangen, den Sie oben haben:

  1. So wie Sie es geschrieben haben, haben diese Versandfunktionen keinen Zugriff auf das Geschäft. Sie können dies etwas umgehen, indem Sie Ihre UI-Komponenten alle Informationen übergeben lassen, die die Dispatching-Funktion benötigt. Ich würde jedoch argumentieren, dass dies diese UI-Komponenten unnötig mit der Dispatching-Logik koppelt. Problematischer ist, dass es für die Dispatching-Funktion keine offensichtliche Möglichkeit gibt, in asynchronen Fortsetzungen auf den aktualisierten Status zuzugreifen.
  2. Die Dispatching-Funktionen haben dispatchüber den lexikalischen Bereich Zugriff auf sich selbst. Dies schränkt die Optionen für das Refactoring ein, sobald diese connectAnweisung außer Kontrolle gerät - und sie sieht mit nur dieser einen updateMethode ziemlich unhandlich aus . Sie benötigen also ein System, mit dem Sie diese Dispatcher-Funktionen zusammenstellen können, wenn Sie sie in separate Module aufteilen.

Zusammengenommen müssen Sie ein System aufbauen, damit dispatchdas Geschäft zusammen mit den Parametern des Ereignisses in Ihre Versandfunktionen eingefügt werden kann. Ich kenne drei vernünftige Ansätze für diese Abhängigkeitsinjektion:

  • Redux-Thunk tut dies auf funktionale Weise, indem es sie an Ihre Thunks weiterleitet (was sie nach Dome-Definitionen überhaupt nicht zu Thunks macht). Ich habe nicht mit den anderen dispatchMiddleware-Ansätzen gearbeitet, aber ich gehe davon aus, dass sie im Grunde gleich sind.
  • React-Redux-Controller macht dies mit einer Coroutine. Als Bonus erhalten Sie auch Zugriff auf die "Selektoren", die Funktionen, an die Sie möglicherweise als erstes Argument übergeben haben connect, anstatt direkt mit dem rohen, normalisierten Speicher arbeiten zu müssen.
  • Sie können dies auch objektorientiert tun, indem Sie sie thisüber eine Vielzahl möglicher Mechanismen in den Kontext einfügen.

Aktualisieren

Mir fällt ein, dass ein Teil dieses Rätsels eine Einschränkung des React -Redux ist . Das erste Argument, um connecteinen Status-Snapshot zu erhalten, aber keinen Versand. Das zweite Argument wird versandt, aber nicht der Staat. Keines der Argumente erhält einen Thunk, der über dem aktuellen Status geschlossen wird, um den aktualisierten Status zum Zeitpunkt einer Fortsetzung / eines Rückrufs anzeigen zu können.


22

Abramovs Ziel - und im Idealfall jeder - ist es einfach, Komplexität (und asynchrone Anrufe) an dem Ort zusammenzufassen, an dem sie am besten geeignet ist .

Wo ist der beste Ort, um dies im Standard-Redux-Datenfluss zu tun? Wie wäre es mit:

  • Reduzierstücke ? Auf keinen Fall. Sie sollten reine Funktionen ohne Nebenwirkungen sein. Das Aktualisieren des Geschäfts ist ein ernstes, kompliziertes Geschäft. Verunreinigen Sie es nicht.
  • Dumme Ansichtskomponenten? Definitiv Nein. Sie haben ein Problem: Präsentation und Benutzerinteraktion und sollten so einfach wie möglich sein.
  • Containerkomponenten? Möglich, aber nicht optimal. Es ist insofern sinnvoll, als der Container ein Ort ist, an dem wir die Komplexität der Ansicht zusammenfassen und mit dem Geschäft interagieren.
    • Container müssen komplexer sein als dumme Komponenten, aber es liegt immer noch in einer einzigen Verantwortung: Bindungen zwischen Ansicht und Status / Speicher bereitzustellen. Ihre asynchrone Logik ist ein ganz anderes Anliegen.
    • Wenn Sie es in einen Container legen, sperren Sie Ihre asynchrone Logik für eine einzelne Ansicht / Route in einen einzigen Kontext. Schlechte Idee. Im Idealfall ist alles wiederverwendbar und vollständig entkoppelt.
  • S ome anderes Service - Modul? Schlechte Idee: Sie müssen den Zugriff auf das Geschäft aktivieren, was ein Albtraum für Wartbarkeit und Testbarkeit ist. Besser mit Redux arbeiten und nur mit den bereitgestellten APIs / Modellen auf den Store zugreifen.
  • Aktionen und die Middlewares, die sie interpretieren? Warum nicht?! Für den Anfang ist es die einzige größere Option, die wir noch haben. :-) Logischerweise ist das Aktionssystem eine entkoppelte Ausführungslogik, die Sie von überall aus verwenden können. Es hat Zugriff auf das Geschäft und kann weitere Aktionen auslösen. Es hat eine einzige Verantwortung, den Kontroll- und Datenfluss um die Anwendung herum zu organisieren, und die meisten Asynchronen passen genau dazu.
    • Was ist mit den Action Creators? Warum nicht einfach dort asynchronisieren, anstatt in den Aktionen selbst und in der Middleware?
      • Erstens und vor allem haben die Ersteller keinen Zugriff auf den Store, wie dies bei Middleware der Fall ist. Das bedeutet, dass Sie keine neuen Kontingentaktionen auslösen können, nicht aus dem Geschäft lesen können, um Ihre Asynchronisierung zu erstellen usw.
      • Halten Sie also die Komplexität an einem Ort, der notwendigerweise komplex ist, und halten Sie alles andere einfach. Die Ersteller können dann einfache, relativ reine Funktionen sein, die leicht zu testen sind.

Containerkomponenten - warum nicht? Aufgrund der Rolle, die Komponenten in React spielen, kann ein Container als Serviceklasse fungieren und erhält bereits einen Speicher über DI (Requisiten). Wenn Sie es in einem Container ablegen, sperren Sie Ihre asynchrone Logik für eine einzelne Ansicht / Route in einen einzigen Kontext - wie? Eine Komponente kann mehrere Instanzen haben. Es kann von der Präsentation entkoppelt werden, zB mit Render Prop. Ich denke, die Antwort könnte noch mehr von kurzen Beispielen profitieren, die den Punkt beweisen.
Estus Flask

Ich liebe diese Antwort!
Mauricio Avendaño

13

Um die am Anfang gestellte Frage zu beantworten:

Warum kann die Containerkomponente die asynchrone API nicht aufrufen und dann die Aktionen auslösen?

Beachten Sie, dass diese Dokumente für Redux und nicht für Redux plus React bestimmt sind. Redux-Stores, die an React-Komponenten angeschlossen sind, können genau das tun, was Sie sagen, aber ein Plain Jane Redux-Store ohne Middleware akzeptiert keine Argumente, dispatchaußer für einfache alte Objekte.

Ohne Middleware könnte man das natürlich noch machen

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

In einem ähnlichen Fall wird die Asynchronität jedoch um Redux gewickelt und nicht von Redux verarbeitet. Middleware ermöglicht also Asynchronität, indem geändert wird, was direkt an übergeben werden kann dispatch.


Der Geist Ihres Vorschlags ist jedoch meiner Meinung nach gültig. Es gibt sicherlich andere Möglichkeiten, wie Sie mit Asynchronität in einer Redux + React-Anwendung umgehen können.

Ein Vorteil der Verwendung von Middleware besteht darin, dass Sie weiterhin wie gewohnt Action Creators verwenden können, ohne sich Gedanken darüber machen zu müssen, wie sie genau angeschlossen sind. Wenn Sie beispielsweise redux-thunkden von Ihnen geschriebenen Code verwenden, sieht er sehr ähnlich aus

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

Das sieht nicht viel anders aus als das Original - es wird nur ein bisschen gemischt - und connectweiß nicht, dass updateThinges asynchron ist (oder sein muss).

Wenn Sie auch Versprechen , Observablen , Sagen oder verrückte benutzerdefinierte und äußerst deklarative Aktionsersteller unterstützen möchten, kann Redux dies tun, indem Sie einfach ändern, an was Sie übergeben dispatch(auch bekannt als das, was Sie von Aktionserstellern zurückgeben). Kein Mist mit den React-Komponenten (oder connectAufrufen) notwendig.


Sie raten, nach Abschluss der Aktion noch ein weiteres Ereignis auszulösen. Dies funktioniert nicht, wenn Sie nach Abschluss der Aktion eine Warnung () anzeigen müssen. Versprechen in React-Komponenten funktionieren jedoch. Ich empfehle derzeit den Promises-Ansatz.
Katamphetamin

8

OK, lassen Sie uns zuerst sehen, wie Middleware funktioniert, die die Frage ganz beantwortet. Dies ist der Quellcode einer pplyMiddleWare- Funktion in Redux:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

Schauen Sie sich diesen Teil an und sehen Sie, wie unser Versand zu einer Funktion wird .

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • Beachten Sie, dass jede Middleware das dispatchund getStateals benannte Argumente erhält .

OK, so stellt sich Redux-thunk als eine der am häufigsten verwendeten Middlewares für Redux vor:

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.

Wie Sie sehen, wird eine Funktion anstelle einer Aktion zurückgegeben. Sie können also warten und sie jederzeit aufrufen, da es sich um eine Funktion handelt.

Also, was zum Teufel ist das? So wird es in Wikipedia eingeführt:

Bei der Computerprogrammierung ist ein Thunk eine Unterroutine, mit der eine zusätzliche Berechnung in eine andere Unterroutine eingefügt wird. Thunks werden hauptsächlich verwendet, um eine Berechnung zu verzögern, bis sie benötigt wird, oder um Operationen am Anfang oder Ende der anderen Unterroutine einzufügen. Sie haben eine Vielzahl anderer Anwendungen zur Generierung von Compilercode und zur modularen Programmierung.

Der Begriff entstand als scherzhafte Ableitung von "denken".

Ein Thunk ist eine Funktion, die einen Ausdruck umschließt, um seine Auswertung zu verzögern.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

Sehen Sie also, wie einfach das Konzept ist und wie es Ihnen bei der Verwaltung Ihrer asynchronen Aktionen helfen kann ...

Das ist etwas, ohne das man leben kann, aber denken Sie daran, dass es beim Programmieren immer bessere, sauberere und richtige Wege gibt, Dinge zu tun ...

Wenden Sie Middleware Redux an


1
Zum ersten Mal auf SO, habe nichts gelesen. Aber ich mochte nur den Beitrag, der das Bild betrachtete. Erstaunlich, Hinweis und Erinnerung.
Bhojendra Rauniyar

2

Die Verwendung von Redux-saga ist die beste Middleware für die Implementierung von React-redux.

Beispiel: store.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

Und dann saga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

Und dann action.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

Und dann reducer.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

Und dann main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

versuchen Sie dies .. funktioniert


3
Dies ist eine ernste Angelegenheit für jemanden, der nur einen API-Endpunkt aufrufen möchte, um eine Entität oder eine Liste von Entitäten zurückzugeben. Sie empfehlen: "Tun Sie einfach das ... dann das, dann das, dann das andere, dann das, dann das andere Zeug, dann fahren Sie fort, dann tun Sie ...". Aber Mann, das ist FRONTEND, wir müssen nur das BACKEND anrufen, um uns Daten zu geben, die für die Verwendung im Frontend bereit sind. Wenn dies der
richtige

Hallo, benutze try and catch block für die API-Aufrufe. Rufen Sie die Aktionstypen Reducer auf, sobald die API die Antwort gegeben hat.
SM Chinna

1
@zameb Sie haben vielleicht Recht, aber Ihre Beschwerde betrifft Redux selbst und all das, was es beim Versuch, die Komplexität zu reduzieren, belauscht.
Jorisw

1

Es gibt synchrone Aktionsersteller und dann asynchrone Aktionsersteller.

Ein synchroner Aktionsersteller ist einer, der beim Aufrufen sofort ein Aktionsobjekt mit allen relevanten Daten zurückgibt, die an dieses Objekt angehängt sind, und bereit ist, von unseren Reduzierungen verarbeitet zu werden.

Asynchrone Aktionsersteller benötigen einige Zeit, bis sie zum endgültigen Versenden einer Aktion bereit sind.

Per Definition gilt ein Aktionsersteller, der eine Netzwerkanforderung stellt, immer als asynchroner Aktionsersteller.

Wenn Sie asynchrone Aktionsersteller in einer Redux-Anwendung haben möchten, müssen Sie eine sogenannte Middleware installieren, mit der Sie mit diesen asynchronen Aktionserstellern umgehen können.

Sie können dies in der Fehlermeldung überprüfen, die besagt, dass wir benutzerdefinierte Middleware für asynchrone Aktionen verwenden.

Was ist eine Middleware und warum benötigen wir sie für den asynchronen Ablauf in Redux?

Im Kontext von Redux-Middleware wie Redux-Thunk hilft uns eine Middleware beim Umgang mit asynchronen Aktionserstellern, da Redux dies nicht sofort tun kann.

Mit einer Middleware, die in den Redux-Zyklus integriert ist, rufen wir immer noch Aktionsersteller an, die eine Aktion zurückgeben, die ausgelöst wird. Wenn wir jedoch eine Aktion senden, anstatt sie direkt an alle unsere Reduzierer zu senden, werden wir loslegen um zu sagen, dass eine Aktion über die gesamte Middleware in der Anwendung gesendet wird.

Innerhalb einer einzelnen Redux-App können wir so viele oder so wenige Middleware haben, wie wir möchten. In den Projekten, an denen wir arbeiten, werden größtenteils eine oder zwei Middleware an unseren Redux-Store angeschlossen.

Eine Middleware ist eine einfache JavaScript-Funktion, die bei jeder einzelnen Aktion, die wir auslösen, aufgerufen wird. Innerhalb dieser Funktion hat eine Middleware die Möglichkeit, zu verhindern, dass eine Aktion an einen der Reduzierer gesendet wird. Sie kann eine Aktion ändern oder einfach mit einer Aktion herumspielen, wie Sie es beispielsweise tun. Wir könnten beispielsweise eine Middleware erstellen, die die Konsole protokolliert Jede Aktion, die Sie nur zu Ihrem Vergnügen versenden.

Es gibt eine enorme Anzahl von Open Source-Middleware, die Sie als Abhängigkeiten in Ihr Projekt installieren können.

Sie können nicht nur Open Source-Middleware verwenden oder diese als Abhängigkeiten installieren. Sie können Ihre eigene benutzerdefinierte Middleware schreiben und in Ihrem Redux-Store verwenden.

Eine der beliebtesten Anwendungen von Middleware (und das Erreichen Ihrer Antwort) ist der Umgang mit asynchronen Aktionserstellern. Die wahrscheinlich beliebteste Middleware auf dem Markt ist Redux-Thunk, und es geht darum, Ihnen beim Umgang mit asynchronen Aktionserstellern zu helfen.

Es gibt viele andere Arten von Middleware, die Ihnen auch beim Umgang mit asynchronen Aktionserstellern helfen.


1

Um die Frage zu beantworten:

Warum kann die Containerkomponente die asynchrone API nicht aufrufen und dann die Aktionen auslösen?

Ich würde aus mindestens zwei Gründen sagen:

Der erste Grund ist die Trennung von Bedenken, es ist nicht die Aufgabe der, die action creatoraufzurufen apiund Daten zurückzubekommen, Sie müssen zwei Argumente an Ihre action creator function, die action typeund eine übergeben payload.

Der zweite Grund ist, dass das redux storeauf ein einfaches Objekt mit obligatorischem Aktionstyp und optional a wartet payload(aber hier müssen Sie auch die Nutzdaten übergeben).

Der Aktionsersteller sollte ein einfaches Objekt wie das folgende sein:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Und die Aufgabe Redux-Thunk midleware, dispachedas Ergebnis von Ihnen api callzu den entsprechenden action.


0

Wenn Sie in einem Unternehmensprojekt arbeiten, stehen in Middleware viele Anforderungen zur Verfügung, z. B. (Saga), die im einfachen asynchronen Ablauf nicht verfügbar sind. Nachfolgend einige:

  • Anfrage parallel ausführen
  • Zukünftige Aktionen ausführen, ohne warten zu müssen
  • Nicht blockierende Anrufe Race-Effekt, Beispiel zuerst abholen
  • Antwort zum Einleiten des Prozesses Sequenzieren Ihrer Aufgaben (zuerst im ersten Aufruf)
  • Komponieren
  • Aufgabenabbruch Dynamisches Verzweigen der Aufgabe.
  • Unterstützung der gleichzeitigen Ausführung von Saga außerhalb der Redux-Middleware.
  • Kanäle verwenden

Die Liste ist lang. Lesen Sie einfach den erweiterten Abschnitt in der Saga-Dokumentation

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.