Redux: Auslösen von Aktionen als Reaktion auf Routenübergänge im React-Router

Erstellt am 7. Juli 2015  ·  26Kommentare  ·  Quelle: reduxjs/redux

Hallo Leute,

Ich verwende React-Router und Redux in meiner neuesten App und habe einige Probleme mit Statusänderungen, die aufgrund der aktuellen URL-Parameter und Abfragen erforderlich sind.

Grundsätzlich habe ich eine Komponente, die ihren Status jedes Mal aktualisieren muss, wenn sich die URL ändert. Der Zustand wird durch Requisiten durch Redux mit dem Dekorateur so weitergegeben

 @connect(state => ({
   campaigngroups: state.jobresults.campaigngroups,
   error: state.jobresults.error,
   loading: state.jobresults.loading
 }))

Im Moment verwende ich die Lebenszyklusmethode componentWillReceiveProps , um auf die vom React-Router kommenden URL-Änderungen zu reagieren, da der React-Router neue Requisiten an den Handler weitergibt, wenn sich die URL in this.props.params und this.props.query ändert

  componentWillReceiveProps(nextProps) {
    if (this.state.shouldupdate) {
      let { slug } = nextProps.params;
      let { citizenships, discipline, workright, location } = nextProps.query;
      const params = { slug, discipline, workright, location };
      let filters = this._getFilters(params);
      // set the state accroding to the filters in the url
      this._setState(params);
      // trigger the action to refill the stores
      this.actions.loadCampaignGroups(filters);
    }
  }

Gibt es einen Standardansatz zum Auslösen von Aktionen basierend auf Routenübergängen ODER kann ich den Status des Geschäfts direkt mit dem Status der Komponente verbinden, anstatt ihn über Requisiten weiterzugeben? Ich habe versucht, die statische Methode willTransitionTo verwenden, habe dort jedoch keinen Zugriff auf this.props.dispatch.

docs ecosystem question

Hilfreichster Kommentar

@deowk Es gibt zwei Teile zu diesem Problem, würde ich sagen. Das erste ist, dass componentWillReceiveProps() kein idealer Weg ist, um auf Statusänderungen zu reagieren - hauptsächlich, weil Sie dazu gezwungen sind, zwingend zu denken, anstatt reaktiv wie bei Redux. Die Lösung besteht darin, Ihre aktuellen Router-Informationen (Speicherort, Parameter, Abfrage) in Ihrem Geschäft zu speichern. Dann befindet sich Ihr gesamter Status an derselben Stelle, und Sie können ihn mit derselben Redux-API wie die übrigen Daten abonnieren.

Der Trick besteht darin, einen Aktionstyp zu erstellen, der ausgelöst wird, wenn sich der Standort des Routers ändert. Dies ist in der kommenden Version 1.0 von React Router ganz einfach:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Jetzt ist Ihr Geschäftsstatus immer mit dem Router-Status synchron. Das behebt die Notwendigkeit, manuell auf Änderungen der Abfrageparameter und setState() in Ihrer obigen Komponente zu reagieren - verwenden Sie einfach den Redux-Connector.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

Der zweite Teil des Problems besteht darin, dass Sie eine Möglichkeit benötigen, auf Änderungen des Redux-Status außerhalb der Ansichtsebene zu reagieren, beispielsweise um eine Aktion als Reaktion auf eine Routenänderung auszulösen. Sie können componentWillReceiveProps weiterhin für einfache Fälle wie den von Ihnen beschriebenen verwenden, wenn Sie dies wünschen.

Für etwas Komplizierteres empfehle ich jedoch die Verwendung von RxJS, wenn Sie dafür offen sind. Genau dafür sind Observablen ausgelegt - ein reaktiver Datenfluss.

Erstellen Sie dazu in Redux zunächst eine beobachtbare Folge von Speicherzuständen. Sie können dies mit observableFromStore() von redux-rx tun.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Dann müssen nur noch beobachtbare Operatoren verwendet werden, um bestimmte Statusänderungen zu abonnieren. Hier ist ein Beispiel für die Umleitung von einer Anmeldeseite nach einer erfolgreichen Anmeldung:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Diese Implementierung ist viel einfacher als die gleiche Funktionalität, bei der zwingende Muster wie componentDidReceiveProps() .

Hoffentlich hilft das!

Alle 26 Kommentare

@deowk Sie können this.props mit nextProps vergleichen, um festzustellen , ob sich die relevanten Daten geändert haben. Wenn es sich nicht geändert hat, müssen Sie die Aktion nicht erneut auslösen und können der Endlosschleife entkommen.

@johanneslumpe : In meinem Fall handelt es sich bei Kampagnengruppen um eine ziemlich große Sammlung von Objekten. Es scheint ineffizient, einen tiefen Objektvergleich als Bedingung auf diese Weise zu verwenden.

@deowk Wenn Sie unveränderliche Daten verwenden, können Sie nur Referenzen vergleichen

@deowk Es gibt zwei Teile zu diesem Problem, würde ich sagen. Das erste ist, dass componentWillReceiveProps() kein idealer Weg ist, um auf Statusänderungen zu reagieren - hauptsächlich, weil Sie dazu gezwungen sind, zwingend zu denken, anstatt reaktiv wie bei Redux. Die Lösung besteht darin, Ihre aktuellen Router-Informationen (Speicherort, Parameter, Abfrage) in Ihrem Geschäft zu speichern. Dann befindet sich Ihr gesamter Status an derselben Stelle, und Sie können ihn mit derselben Redux-API wie die übrigen Daten abonnieren.

Der Trick besteht darin, einen Aktionstyp zu erstellen, der ausgelöst wird, wenn sich der Standort des Routers ändert. Dies ist in der kommenden Version 1.0 von React Router ganz einfach:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

Jetzt ist Ihr Geschäftsstatus immer mit dem Router-Status synchron. Das behebt die Notwendigkeit, manuell auf Änderungen der Abfrageparameter und setState() in Ihrer obigen Komponente zu reagieren - verwenden Sie einfach den Redux-Connector.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

Der zweite Teil des Problems besteht darin, dass Sie eine Möglichkeit benötigen, auf Änderungen des Redux-Status außerhalb der Ansichtsebene zu reagieren, beispielsweise um eine Aktion als Reaktion auf eine Routenänderung auszulösen. Sie können componentWillReceiveProps weiterhin für einfache Fälle wie den von Ihnen beschriebenen verwenden, wenn Sie dies wünschen.

Für etwas Komplizierteres empfehle ich jedoch die Verwendung von RxJS, wenn Sie dafür offen sind. Genau dafür sind Observablen ausgelegt - ein reaktiver Datenfluss.

Erstellen Sie dazu in Redux zunächst eine beobachtbare Folge von Speicherzuständen. Sie können dies mit observableFromStore() von redux-rx tun.

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

Dann müssen nur noch beobachtbare Operatoren verwendet werden, um bestimmte Statusänderungen zu abonnieren. Hier ist ein Beispiel für die Umleitung von einer Anmeldeseite nach einer erfolgreichen Anmeldung:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

Diese Implementierung ist viel einfacher als die gleiche Funktionalität, bei der zwingende Muster wie componentDidReceiveProps() .

Hoffentlich hilft das!

Wir brauchen dies in neuen Dokumenten. So gut geschrieben.

@acdlite : Vielen Dank, Ihr Rat hat mir sehr geholfen. Ich habe jedoch mit folgenden

Ich möchte den Aktionsersteller in der statischen Methode willTransitionTo auslösen, da dies die einzige Option in React-Router Version 0.13.3 zu sein scheint

class Results extends Component  {
..........
}

Results.willTransitionTo = function (transition, params, query) {
 // how do I get a reference to dispatch here?
};

@deowk Ich vermute, dass Sie dispatch direkt auf der Redux-Instanz aufrufen:

const redux = createRedux(stores);
BrowserHistory.listen(location => redux.dispatch(routeLocationDidUpdate(location)));

@deowk Ich versende derzeit meine URL-Änderungen in React Router 0.13.3 wie folgt:

Router.run(routes, Router.HistoryLocation, function(Handler, locationState) {
   dispatch(routeLocationDidUpdate(locationState))
   React.render(<Handler/>, appEl);
});

@acdlite wirklich gut erklärt! : +1:
Stimmen Sie zu, dass es auch in den neuen Dokumenten sein sollte :)

Nur als Hinweis wurde BrowserHistory.history() in BrowserHistory.addChangeListener() geändert.

https://github.com/rackt/react-router/blob/master/modules/BrowserHistory.js#L57

BEARBEITEN:

Welches könnte so aussehen:

history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

Und was ist mit dem Ausgangszustand für diesen Reduzierer, @pburtchaell?

Ich habe folgendes Setup:

// client.js
class App extends Component {
  constructor(props) {
    super(props);
    this.history = new HashHistory();
  }

  render() {
    return (
      <Provider store={store}>
         {renderRoutes.bind(null, this.history)}
      </Provider>
    );
  }
}

// routes.js
export default function renderRoutes(history) {

  // When the route changes, dispatch that information to the store.
  history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

  return (
    <Router history={history}>
      <Route component={myAppView}>
        <Route path="/" component={myHomeView}  />
      </Route>
    </Router>
  );
};

Dadurch wird der Aktionsersteller routeLocationDidUpdate() jedes Mal ausgelöst, wenn sich die Route ändert und wenn die App zum ersten Mal geladen wird.

@pburtchaell kannst du deine routeLocationDidUpdate teilen?

@gyzerok Sicher. Es ist ein Action-Schöpfer.

// actions/location.js
export function routeLocationDidUpdate(location) {
  return {
    type: 'LOCATION_UPDATE',
    payload: {
      ...location,
    },
  };
}

@pburtchaell also in deinem Beispiel verstehe ich nicht ganz, wo du Daten abrufst, die für eine bestimmte Route notwendig sind

Hier ist eine ausführlichere Version meines obigen Beispiels:

https://github.com/acdlite/redux-react-router/blob/master/README.md#bonus -reagieren auf Statusänderungen mit Redux-RX

Ich gehe davon aus, dass abzurufen (von einem REST-Aufruf usw.). Was ich mir nicht sicher bin, ist was dann? Ich nehme an, es geht dann in ein Geschäft, aber wäre dieses Geschäft ein locationStore, das ich dann

<strong i="7">@connect</strong>

zu in, sagen wir in

<Comments />

Was wäre bereits mit einem commentStore 'verbunden' (abonniert)? Oder fügen Sie einfach diese Standortaktionen im KommentareStore registrieren hinzu?

Eine URL wie

/comments/123

wird immer mit der Kommentarkomponente "gekoppelt" sein. Wie kann ich sie also wiederverwenden? Entschuldigung, wenn das dumm ist, hier sehr verwirrt. Welches es ein solides Beispiel gab, das ich finden konnte.

Ich setze mich auch damit auseinander und finde heraus, wie ich meine statische fetchData(dispatch, params) -Methode aufrufen kann (statisch, weil der Server sie aufruft, um asynchrone FETCH-Aktionen vor dem ersten Rendern auszulösen).

Da im Kontext ein Verweis auf dispatch vorhanden ist, denke ich, dass der sauberste Weg, dies für clientseitige Aufrufe von fetchData eine Connector -ähnliche Komponente ist, die aufruft fetchData oder ein shouldFetchData , oder lassen Sie den select Rückruf einen Verweis auf dispatch zusammen mit state , damit wir den aktuellen Stand überprüfen können Status von store und Versand von FETCH Aktionen nach Bedarf.

Letzteres ist prägnanter, bricht jedoch die Tatsache, dass select eine reine Funktion bleiben sollte. Ich werde in die erstere schauen.

Meine Lösung lautet wie folgt: Obwohl sie auf meine Einrichtung zugeschnitten ist (statische fetchData(dispatch, state) -Methode, die asynchrone _FETCH -Aktionen auslöst), ruft sie alle an sie übergebenen Rückrufe mit den entsprechenden Eigenschaften auf: https: // gist.github.com/grrowl/6cca2162e468891d8128 - verwendet jedoch nicht willTransitionTo, sodass Sie den Übergang von Seiten nicht verzögern können.

Wir müssen definitiv die Dokumente hinzufügen! Möglich im Abschnitt "Mit React-Router verwenden".

Nach der Veröffentlichung von 1.0 haben wir eine offizielle Möglichkeit, dies zu tun.

Schön, @gaearon zu hören.

Dann müssen nur noch beobachtbare Operatoren verwendet werden, um bestimmte Statusänderungen zu abonnieren.

Ich habe einen beobachtbaren Stream eingerichtet, um mein Geschäft mit allen Routenänderungen synchron zu halten, und ich habe ein Stream-Setup, um auch mein Geschäft zu überwachen. Ich bin daran interessiert, zu einer neuen Route zu wechseln, wenn sich ein Wert in meinem Geschäft ändert, insbesondere wenn er sich von undefined zu einer gültigen Zeichenfolge ändert, weil ein Formular erfolgreich an einer anderen Stelle in meiner App gesendet wurde.

Die beiden Streams sind eingerichtet und funktionieren, und der Store wird aktualisiert, aber ich bin neu bei Rx und habe Probleme mit der beobachtbaren Abfrage ... irgendwelche Hinweise? Bin ich auf dem richtigen Weg? Ich bin mir ziemlich sicher, dass es etwas mit distinctUntilChanged zu tun hat, aber es backt im Moment meine Nudeln, jede Hilfe wird geschätzt :)

EDIT: Ack! Entschuldigung, beantwortete meine eigene Frage. Pseudocode unten. Lass es mich wissen, wenn es schrecklich aussieht?

const didChangeProject$ = state$
  .distinctUntilChanged(state => state.project._id)
  .filter(state => typeof state.project._id !== 'undefined');

Schließen zugunsten von # 177. Wenn wir das Routing dokumentieren möchten, müssen wir alle Szenarien abdecken: Umleitung bei Statusänderung, Umleitung bei Rückruf auf Anfrage usw.

Ich weiß, dass dies geschlossen ist. Aber mit React 0.14 und den verschiedenen 1.0-Abhängigkeiten in der Beta gibt es derzeit ein Update dafür und wenn nicht, kann ein Tutorial über die neuen Fähigkeiten in React 0.14, React-Router, Redux usw. sein geschrieben? Es scheint viele von uns zu geben, die versuchen, über die bevorstehenden Änderungen auf dem Laufenden zu bleiben, während sie dies alles gleichzeitig lernen / verstehen. Ich weiß, dass die Dinge im Fluss sind (Wortspiel ... ähm ... nicht beabsichtigt), aber es scheint, dass ich Stücke aus verschiedenen Beispielen sehe, die nicht gut miteinander spielen, und nach einigen Tagen kann ich meine aktualisierte App immer noch nicht zum Laufen bringen mit Routing. Höchstwahrscheinlich meine eigene Schuld, da ich immer noch Probleme damit habe zu verstehen, wie das alles funktioniert, und nur hoffe, dass bald ein aktualisiertes Tutorial mit den neuesten Informationen herauskommt.

Bis es ein Tutorial gibt, können Sie sich examples/real-world ansehen, das das Routing enthält. Es ist momentan nicht "neu und am besten", aber es funktioniert gut.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen