Redux: Der Versuch, API-Aufrufe an der richtigen Stelle zu platzieren

Erstellt am 20. Juli 2015  ·  115Kommentare  ·  Quelle: reduxjs/redux

Ich versuche, einen Anmeldeerfolg / -fehler zu erzielen, aber mein Hauptanliegen ist, wo ich diese Logik platzieren kann.

Derzeit verwende ich actions -> reducer (switch case with action calling API) -> success/error on response triggering another action .

Das Problem bei diesem Ansatz ist, dass der Reduzierer nicht funktioniert, wenn ich die Aktion über den API-Aufruf aufrufe.

vermisse ich etwas

Reduzierstück

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';
import Immutable from 'immutable';
import LoginApiCall from '../utils/login-request';

const initialState = new Immutable.Map({
  email: '',
  password: '',
}).asMutable();

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user);
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }
}

Aktionen

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

/*
 * Should add the route like parameter in this method
*/
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard'); // will fire CHANGE_ROUTE in its change handler
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

API-Aufrufe

 // Use there fetch polyfill
 // The main idea is create a helper in order to handle success/error status
import * as LoginActions from '../actions/LoginActions';

const LoginApiCall = {
  login(userData) {
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        LoginActions.loginSuccess(response);
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        LoginActions.loginError();
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
  },
};

export default LoginApiCall;
docs question

Hilfreichster Kommentar

Wo platzieren Sie dann den Login-API-Aufruf?

Genau dafür ist dispatch => {} Action Creator gedacht. Nebenwirkungen!

Es ist nur ein weiterer Action-Schöpfer. Stellen Sie es mit anderen Aktionen zusammen:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

Rufen Sie in Ihren Komponenten einfach an

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

Alle 115 Kommentare

Dies ist fast richtig, aber das Problem ist, dass Sie nicht einfach reine Action-Ersteller anrufen und erwarten können, dass etwas passiert. Vergessen Sie nicht, dass Ihre Aktionsersteller nur Funktionen sind, die angeben, was gesendet werden soll.

// CounterActions
export function increment() {
  return { type: INCREMENT }
}


// Some other file
import { increment } from './CounterActions';
store.dispatch(increment()); <--- This will work assuming you have a reference to the Store
increment(); <---- THIS DOESN'T DO ANYTHING! You're just calling your function and ignoring result.


// SomeComponent
import { increment } from './CounterActions';

@connect(state => state.counter) // will inject store's dispatch into props
class SomeComponent {
  render() {
    return <OtherComponent {...bindActionCreators(CounterActions, this.props.dispatch)} />
  }
}


// OtherComponent
class OtherComponent {
  handleClick() {
    // this is correct:
    this.props.increment(); // <---- it was bound to dispatch in SomeComponent

    // THIS DOESN'T DO ANYTHING:
    CounterActions.increment(); // <---- it's just your functions as is! it's not bound to the Store.
  }
}

Kommen wir nun zu Ihrem Beispiel. Das erste, was ich klarstellen möchte, ist, dass Sie kein asynchrones dispatch => {} -Formular benötigen, wenn Sie nur eine einzelne Aktion synchron auslösen und keine Nebenwirkungen haben (gilt für loginError und loginRequest ).

Diese:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return dispatch => {
    dispatch({ error, type: LOGGED_FAILED });
  };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return dispatch => {
    dispatch({ user, type: LOGIN_ATTEMPT });
  };
}

kann vereinfacht werden als

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

// You'll have a side effect here so (dispatch) => {} form is a good idea
export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    // router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

Zweitens sollen Ihre Reduzierer reine Funktionen sein, die keine Nebenwirkungen haben. Versuchen Sie nicht, Ihre API von einem Reduzierer aus aufzurufen.

Diese:

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
}).asMutable(); // <---------------------- why asMutable?

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      console.log(action.user);
      LoginApiCall.login(action.user); // <------------------------ no side effects in reducers! :-(
      return state;
    case LOGGED_FAILED:
      console.log('failed from reducer');
      return state;
    case LOGGED_SUCCESSFULLY:
      console.log('success', action);
      console.log('success from reducer');
      break;
    default:
      return state;
  }

sollte wohl eher so aussehen

const initialState = new Immutable.Map({
  email: '',
  password: '',
  isLoggingIn: false,
  isLoggedIn: false,
  error: null
});

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return state.merge({
        isLoggingIn: true,
        isLoggedIn: false,
        email: action.email,
        password: action.password // Note you shouldn't store user's password in real apps
      });
    case LOGGED_FAILED:
      return state.merge({
        error: action.error,
        isLoggingIn: false,
        isLoggedIn: false
      });
    case LOGGED_SUCCESSFULLY:
      return state.merge({
        error: null,
        isLoggingIn: false,
        isLoggedIn: true
      });
      break;
    default:
      return state;
  }

Wo platzieren Sie dann den Login-API-Aufruf?

Genau dafür ist dispatch => {} Action Creator gedacht. Nebenwirkungen!

Es ist nur ein weiterer Action-Schöpfer. Stellen Sie es mit anderen Aktionen zusammen:

import { LOGIN_ATTEMPT, LOGGED_FAILED, LOGGED_SUCCESSFULLY } from '../constants/LoginActionTypes';

export function loginError(error) {
  return { error, type: LOGGED_FAILED };
}

export function loginSuccess(response) {
  return dispatch => {
    dispatch({ response, type: LOGGED_SUCCESSFULLY });
    router.transitionTo('/dashboard');
  };
}

export function loginRequest(email, password) {
  const user = {email: email, password: password};
  return { user, type: LOGIN_ATTEMPT };
}

export function login(userData) {
  return dispatch =>
    fetch('http://localhost/login', {
      method: 'post',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: userData.email,
        password: userData.password,
      }),
    })
    .then(response => {
      if (response.status >= 200 && response.status < 300) {
        console.log(response);
        dispatch(loginSuccess(response));
      } else {
        const error = new Error(response.statusText);
        error.response = response;
        dispatch(loginError(error));
        throw error;
      }
    })
    .catch(error => { console.log('request failed', error); });
}

Rufen Sie in Ihren Komponenten einfach an

this.props.login(); // assuming it was bound with bindActionCreators before

// --or--

this.props.dispatch(login()); // assuming you only have dispatch from Connector

Wenn Sie häufig große Action-Ersteller wie diesen schreiben, ist es eine gute Idee, eine benutzerdefinierte Middleware für asynchrone Aufrufe zu schreiben, die mit Versprechungen kompatibel ist, was auch immer Sie für asynchrone verwenden.

Die oben beschriebene Technik (Aktionsersteller mit dispatch => {} Signatur) ist derzeit in Redux enthalten, in 1.0 ist sie jedoch nur als separates Paket mit dem Namen Redux-Thunk verfügbar. Wenn Sie schon dabei sind, können Sie sich auch die Redux-Versprechen-Middleware oder Redux-Versprechen ansehen .

@gaearon : klatschen: das war eine erstaunliche Erklärung! ;) Es sollte definitiv in die Dokumentation kommen.

@gaearon tolle Erklärung! :Trophäe:

@gaearon Was ist, wenn Sie eine Logik ausführen müssen, um zu bestimmen, welcher API-Aufruf aufgerufen werden soll? Ist es nicht die Domänenlogik, die sich an einem Ort befinden sollte (Reduzierer)? Das in Aktion zu halten, klingt für mich so, als würde ich eine einzige Quelle der Wahrheit brechen. Außerdem müssen Sie den API-Aufruf meistens anhand eines Werts aus dem Anwendungsstatus parametrisieren. Sie möchten höchstwahrscheinlich auch die Logik irgendwie testen. Wir fanden es sehr hilfreich und testbar , API-Aufrufe in Reduzierern (Atomic Flux) in Großprojekten durchzuführen.

Wir fanden es sehr hilfreich und testbar, API-Aufrufe in Reduzierern (Atomic Flux) in Großprojekten durchzuführen.

Dies bricht die Aufzeichnung / Wiedergabe. Funktionen mit Nebenwirkungen sind per Definition schwerer zu testen als reine Funktionen. Sie können Redux so verwenden, aber es ist völlig gegen sein Design. :-)

Das in Aktion zu halten, klingt für mich so, als würde ich eine einzige Quelle der Wahrheit brechen.

"Eine einzige Quelle der Wahrheit" bedeutet, dass Daten an einem Ort

Was ist, wenn Sie eine Logik ausführen müssen, um zu bestimmen, welcher API-Aufruf aufgerufen werden soll? Ist es nicht die Domänenlogik, die sich an einem Ort befinden sollte (Reduzierer)?

Reduzierer geben an, wie der Status durch Aktionen transformiert wird. Sie sollten sich keine Gedanken darüber machen, woher diese Aktionen stammen. Sie können von Komponenten, Aktionserstellern, aufgezeichneten serialisierten Sitzungen usw. stammen. Dies ist das Schöne am Konzept der Verwendung von Aktionen.

Alle API-Aufrufe (oder Logik, die bestimmt, welche API aufgerufen wird) erfolgen vor Reduzierern. Aus diesem Grund unterstützt Redux Middleware. Mit der oben beschriebenen Thunk-Middleware können Sie Bedingungen verwenden und sogar aus dem Status lesen:

// Simple pure action creator
function loginFailure(error) {
  return { type: LOGIN_FAILURE, error };
}

// Another simple pure action creator
function loginSuccess(userId) {
  return { type: LOGIN_SUCCESS, userId };
}

// Another simple pure action creator
function logout() {
  return { type: LOGOUT };  
}


// Side effect: uses thunk middleware
function login() {
  return dispatch => {
    MyAwesomeAPI.performLogin().then(
      json => dispatch(loginSuccess(json.userId)),
      error => dispatch(loginFailure(error))
    )
  };
}


// Side effect *and* reads state
function toggleLoginState() {
  return (dispatch, getState) => {
    const { isLoggedIn } = getState().loginState;
    if (isLoggedIn) {
      dispatch(login());
    } else {
      dispatch(logout());
    }
  };
}

// Component
this.props.toggleLoginState(); // Doesn't care how it happens

Action Creators und Middleware sind so konzipiert, dass sie Nebenwirkungen hervorrufen und sich gegenseitig ergänzen.
Reduzierstücke sind nur Zustandsmaschinen und haben nichts mit Async zu tun.

Reduzierer geben an, wie der Status durch Aktionen transformiert wird.

Dies ist in der Tat ein sehr gültiger Punkt, danke.

Ich werde für die Nachwelt wieder öffnen, bis eine Version davon in den Dokumenten ist.

: +1:

Ich würde argumentieren, dass Reduzierer zwar Zustandsmaschinen sind, aber auch Aktionsersteller im Allgemeinen (aber implizit).

Angenommen, der Benutzer hat zweimal auf die Schaltfläche "Senden" geklickt. Zum ersten Mal senden Sie eine HTTP-Anfrage an den Server (im Aktionsersteller) und ändern den Status in "Senden" (im Reduzierer). Zum zweiten Mal willst du das nicht.

Im aktuellen Modell muss Ihr Aktionsersteller abhängig vom aktuellen Status auswählen, welche Aktionen gesendet werden sollen. Wenn es derzeit nicht "sendet" - dann senden Sie eine HTTP-Anfrage und senden Sie eine Aktion aus, andernfalls - tun Sie einfach nichts oder senden Sie sogar eine andere Aktion (wie eine Warnung).

Ihr Kontrollfluss ist also effektiv in zwei Zustandsautomaten aufgeteilt, von denen eine implizit und voller Nebenwirkungen ist.

Ich würde sagen, dass dieser Flux-Ansatz die meisten Anwendungsfälle einer typischen Web-App sehr gut behandelt. Wenn Sie jedoch eine komplexe Steuerlogik haben, kann dies problematisch werden. Alle vorhandenen Beispiele haben einfach keine komplexe Steuerlogik, daher ist dieses Problem etwas verborgen.

https://github.com/Day8/re-frame ist ein Beispiel für einen anderen Ansatz, bei dem ein einzelner Ereignishandler als einziger FSM fungiert.

Aber dann haben Reduzierer Nebenwirkungen. Daher ist es interessant, wie sie in solchen Fällen mit der Funktion "Wiedergabe" umgehen - fragen Sie sie unter https://github.com/Day8/re-frame/issues/86

Im Allgemeinen denke ich, dass dies ein echtes Problem ist und es ist keine Überraschung, dass es immer wieder auftaucht. Es ist interessant zu sehen, welche Lösungen irgendwann entstehen werden.

Halten Sie uns über Re-Frame und Nebenwirkungen auf dem Laufenden!

In meinem Buch gibt es 3 Statustypen in jeder anständigen Redux-Web-App.

1) Komponenten anzeigen (Reagieren Sie auf diesen Zustand). Dies sollte vermieden werden, aber manchmal möchte ich nur ein Verhalten implementieren, das wiederverwendet werden kann, indem nur eine Komponente unabhängig von Redux wiederverwendet wird.

2) Gemeinsamer Status der App, dh Redux.state. Es ist der Status, den eine Ansichtsebene verwendet, um einem Benutzer eine App zu präsentieren, und Ansichtskomponenten verwenden sie, um untereinander zu "kommunizieren". Und dieser Status kann verwendet werden, um zwischen ActionCreators, Middlewares und Ansichten zu "kommunizieren", da ihre Entscheidungen von diesem Status abhängen können. Daher ist das "geteilte" wichtig. Es muss jedoch kein vollständiger App-Status sein. Oder ich denke, es ist unpraktisch, alles als diese Art von Zustand (in Bezug auf Aktionen) umzusetzen.

3) Zustand, der von anderen nebenwirkenden komplexen Modulen / Bibliotheken / Diensten gehalten wird. Ich würde diese schreiben, um Szenarien zu behandeln, die vladar beschreibt. Oder React-Router ist ein weiteres Beispiel. Sie können es als Black-Box behandeln, die einen eigenen Status hat, oder Sie können einen Teil des React-Router-Status in den gemeinsam genutzten App-Status (Redux.state) versetzen. Wenn ich ein komplexes HTTP-Modul schreiben würde, das alle meine Anforderungen und deren Timings geschickt verarbeitet, bin ich normalerweise nicht an Details zum Anforderungszeitpunkt in Redux.state interessiert. Ich würde dieses Modul nur von ActionCreators / Middlewares verwenden, möglicherweise zusammen mit Redux.state, um neues Redux.state zu erhalten.

Wenn ich eine Ansichtskomponente schreiben möchte, die die Timings meiner Anfrage zeigt, müsste ich diesen Status auf Redux.state setzen.

@vladar Meinst du damit, dass AC / MW implizite Zustandsmaschinen sind? Das, weil sie keinen Zustand für sich haben, aber immer noch von einem Zustand abhängig sind, der woanders gehalten wird, und dass sie die Steuerlogik definieren können, wie sich diese Zustände im Laufe der Zeit entwickeln? In einigen Fällen denke ich, dass sie immer noch als Schließungen implementiert werden könnten und ihren eigenen Zustand behalten und zu expliziten Zustandsmaschinen werden könnten?

Alternativ könnte ich Redux.state als "öffentlichen Staat" bezeichnen, während andere Staaten privat sind. Das Entwerfen meiner App ist eine Handlung, um zu entscheiden, was als privater Staat und was als öffentlicher Staat beibehalten werden soll. Es scheint mir eine gute Gelegenheit zur Einkapselung zu sein, daher sehe ich es nicht als problematisch an, den Staat in verschiedene Orte zu unterteilen, soweit es nicht zur Hölle wird, wie sie sich gegenseitig beeinflussen.

@vladar

Aber dann haben Reduzierer Nebenwirkungen. Es ist also interessant, wie sie in solchen Fällen mit der Funktion "Wiedergabe" umgehen

Um eine einfache Wiedergabe zu erreichen, muss der Code deterministisch sein. Dies erreicht Redux, indem es reine Reduzierstücke benötigt. In einem Effekt unterteilt Redux.state die App in nicht deterministische (asynchrone) und deterministische Teile. Sie können das Verhalten über Redux.state wiedergeben, vorausgesetzt, Sie machen keine verrückten Dinge in Ansichtskomponenten. Das erste wichtige Ziel, um deterministischen Code zu erreichen, besteht darin, asynchronen Code zu entfernen und ihn über das Aktionsprotokoll in synchronen Code zu übersetzen. Dies ist, was die Flux-Architektur im Allgemeinen tut. Im Allgemeinen reicht dies jedoch nicht aus, und andere nebeneffektive oder mutierende Codes können die deterministische Verarbeitung immer noch unterbrechen.

Das Erreichen der Wiederholbarkeit mit nebenwirkenden Reduzierern ist imho entweder unpraktisch schwer, sogar unmöglich, oder es funktioniert nur teilweise bei vielen Eckfällen mit wahrscheinlich geringem praktischen Effekt.

Um einen einfachen Zeitplan zu erreichen, speichern wir Snapshots des App-Status, anstatt Aktionen (die immer schwierig sind) wie in Redux wiederzugeben.

Um einen einfachen Zeitplan zu erreichen, speichern wir Snapshots des App-Status, anstatt Aktionen (die immer schwierig sind) wie in Redux wiederzugeben.

Redux DevTools speichern sowohl Snapshots als auch Aktionen. Wenn Sie keine Aktionen haben, können Sie die Reduzierungen nicht im laufenden Betrieb neu laden. Dies ist die leistungsstärkste Funktion des Workflows, die DevTools ermöglicht.

Zeitreisen funktionieren gut mit unreinen Aktionserstellern, da sie auf der Ebene der versendeten (endgültigen) Rohaktionen stattfinden. Dies sind einfache Objekte, daher sind sie vollständig deterministisch. Ja, Sie können einen API-Aufruf nicht zurücksetzen, aber ich sehe kein Problem damit. Sie können die von ihm ausgegebenen Rohaktionen zurücksetzen.

Das, weil sie keinen Zustand für sich haben, aber immer noch von einem Zustand abhängig sind, der woanders gehalten wird, und dass sie die Steuerlogik definieren können, wie sich diese Zustände im Laufe der Zeit entwickeln?

Ja, genau das meine ich. Es kann aber auch vorkommen, dass Sie Ihre Steuerlogik duplizieren. Code zur Demonstration des Problems (aus meinem obigen Beispiel mit Doppelklick).

function submit(data) {
  return (dispatch, getState) => {
    const { isSubmitting } = getState().isSubmitting;
    if (!isSubmitting) {
      dispatch(started(data));

      MyAPI.submit(data).then(
        json => dispatch(success(json)),
        error => dispatch(failure(error))
      )
    }
  }
}

function started(data) {
   return { type: SUBMIT_STARTED, data };
}

function success(result) {
  return { type: SUBMIT_SUCCESS, result };
}

function failure(error) {
  return { type: SUBMIT_FAILURE, error };
}

Und dann haben Sie Ihren Reduzierer, um den Status "isSubmission" erneut zu überprüfen

const initialState = new Immutable.Map({
  isSubmitting: false,
  pendingData: null,
  error: null
});

export default function form(state = initialState, action) {
  switch (action.type) {
    case SUBMIT_STARTED:
      if (!state.isSubmitting) {
        return state.merge({
          isSubmitting: true,
          pendingData: action.data
        });
      } else {
        return state;
      }
  }
}

Sie haben also an zwei Stellen die gleichen logischen Prüfungen. Offensichtlich ist die Duplizierung in diesem Beispiel minimal, aber für komplexere Szenarien kann es nicht schön werden.

Ich denke im letzten Beispiel sollte der Scheck nicht im Reduzierer sein. Andernfalls kann es schnell zu Inkonsistenzen kommen (z. B. wird die Aktion versehentlich ausgelöst, aber ignoriert, aber dann wird auch die Aktion "Erfolg" ausgelöst, aber sie war unerwartet und der Status wird möglicherweise falsch zusammengeführt).

(Entschuldigung, wenn das der Punkt war, den du gemacht hast ;-)

Ich bin mit gaeron einverstanden, da das! IsSubmitting bereits in der Tatsache codiert ist, dass die Aktion SUBMIT_STARTED ausgelöst wurde. Wenn eine Aktion das Ergebnis einer Logik ist, sollte diese Logik nicht im Reduzierer repliziert werden. Dann ist die Verantwortung des Reduzierers nur so, dass er, wenn er SUBMIT_STARTED empfängt, nicht denkt und nur den Status mit der Nutzlast der Aktion aktualisiert, weil jemand anderes die Verantwortung übernommen hat, über SUBMIT_STARTED zu entscheiden.

Man sollte immer darauf abzielen, dass es eine einzige Sache gibt, die die Verantwortung für eine einzige Entscheidung übernimmt.

Der Reduzierer kann dann weiter auf der Tatsache SUBMIT_STARTED aufbauen und diese mit zusätzlicher Logik erweitern, aber diese Logik sollte anders sein. Fe:

case SUBMIT_STARTED:
  if (goodMood) loading...
  else nothing_happens

Ich kann mir vorstellen, dass es manchmal schwierig sein kann, zu entscheiden, wer für was verantwortlich sein soll. ActionCreator, Middleware, eigenständiges komplexes Modul, Reduzierer?

Ich würde versuchen, so viele Entscheidungen wie möglich in Bezug auf Reduzierungen zu treffen, während sie rein bleiben, da sie heiß nachgeladen werden können. Wenn Entscheidungen für Nebenwirkungen / Asynchronität erforderlich sind, gehen sie zu AC / MW / irgendwo anders. Es kann auch davon abhängen, wie sich Best Practices in Bezug auf die Wiederverwendung von Code entwickeln, dh Reduzierungen im Vergleich zu Middleware.

AC kann state nicht mutieren. Wird direkt gesendet. Wenn seine Logik davon abhängt, muss AC sicherstellen, dass state.isSubmission mit seiner Logik synchronisiert ist. Wenn die AC-Logik state.isSubmitted mit neuen Daten auf true setzen möchte und Reads zurück ist, wird erwartet, dass es als solches gesetzt ist. Es sollte nicht durch andere Logik möglich sein, diese Prämisse zu ändern. ACTION ist das Synchronisationsprimitiv selbst. Grundsätzlich definieren Aktionen Protokolle und Protokolle sollten gut und klar definiert sein.

Aber ich glaube ich weiß was du versuchst zu sagen. Redux.state ist ein gemeinsam genutzter Status. Geteilter Zustand ist immer schwierig. Es ist einfach, Code in Reduzierungen zu schreiben, die den Zustand auf eine Weise ändern, die von der AC-Logik unerwartet ist. Daher kann ich mir vorstellen, dass es in einigen sehr komplexen Szenarien schwierig sein kann, die Logik zwischen Wechselstrom und Reduzierern synchron zu halten. Dies kann zu Fehlern führen, die möglicherweise schwer zu verfolgen und zu debuggen sind. Ich glaube, dass es für solche Szenarien immer möglich ist, die Logik auf AC / Middleware zu reduzieren und Reduzierungen nur dumm zu machen, basierend auf genau definierten Aktionen ohne oder mit wenig Logik.

Jemand kann immer einen neuen Reduzierer hinzufügen, der einen alten abhängigen Wechselstrom zerstört. Man sollte zweimal überlegen, bevor man AC von Redux.state abhängig macht.

Ich habe einen Aktionsersteller, für den ein Authentifizierungstoken erforderlich ist, das von einer API-Anforderung mit Benutzeranmeldeinformationen zurückgegeben wird. Dieses Token ist zeitlich begrenzt und muss von etwas aktualisiert und verwaltet werden. Was Sie vorschlagen, scheint darauf hinzudeuten, dass dieser Status nicht zu Redux.state gehört?

Wohin soll es gehen? Wird es konzeptionell als unveränderlicher äußerer Zustand an den Konstruktor des Redux-Speichers übergeben?

Daher kann ich mir vorstellen, dass es in einigen sehr komplexen Szenarien schwierig sein kann, die Logik zwischen Wechselstrom und Reduzierern synchron zu halten.

Ja. Ich sage nicht, dass es ein Show-Stopper ist, aber es kann inkonsistent werden. Der Punkt der Zustandsmaschine besteht darin, auf Ereignisse zu reagieren - unabhängig von der Reihenfolge ihres Auftretens - und konsistent zu bleiben.

Wenn Sie sich auf die richtige Reihenfolge der Ereignisse von AC verlassen, verschieben Sie implizit Teile Ihrer State Engine in AC und koppeln sie mit dem Reduzierer.

Vielleicht ist es nur eine Frage der Präferenz, aber ich fühle mich viel besser, wenn Store / Reducer immer konsequent agiert und sich nicht auf etwas Äußeres verlässt, um die Konsistenz woanders aufrechtzuerhalten.

Man sollte zweimal überlegen, bevor man AC von Redux.state abhängig macht.

Ich denke, Sie können es in komplexen Fällen nicht vermeiden (ohne einige Nebenwirkungen auf Reduzierer / Event-Handler zu übertragen).

Es sieht so aus, als wäre es ein Kompromiss: "Hot-Reloading- oder Wiederholungsfunktion löschen" und "Status an einem Ort verwalten". Im späteren Fall können Sie echte FSMs oder Statecharts schreiben, wie es der Re-Frame vorschlägt. Im früheren Fall handelt es sich um eine Ad-hoc-Steuerlogik, wie wir sie jetzt in Flux haben.

Ich habe Re-Frame (zu schnell) durchgelesen und es ist dasselbe wie Redux mit einigen Unterschieden bei den Implementierungsdetails. Re-Frame-Ansichtskomponenten basieren auf Observablen, Ereignisse sind Aktionen, Re-Frame sendet Ereignisse (Aktionen) direkt als Objekte (sie werden in einer Ansichtskomponente erstellt) ohne Verwendung von Erstellern, Event-Handler sind rein und sie sind dasselbe wie Redux Reduzierstücke.

Der Umgang mit Nebenwirkungen wird nicht viel diskutiert oder ich habe das verpasst, aber ich gehe davon aus, dass sie in Middlewares behandelt werden. Und da ist der Hauptunterschied. Middlewares verpacken Event-Handler (Reduzierer) mit ihnen von außen, während Redux Middlewares keine Reduzierer verpacken. Mit anderen Worten, Re-Frame komponiert Nebenwirkungen direkt zu reinen Reduzierern, während Redux sie getrennt hält.

Dies bedeutet, dass man Re-Frame-Ereignisse nicht aufzeichnen und einfach wiedergeben kann.

Als ich über mögliche Inkonsistenzen sprach, betrachtete ich den gemeinsamen Zustand als Grundursache und glaube, dass Re-Frame das gleiche Risiko birgt.

Der Unterschied besteht in der Kopplung von Middlewares mit Reduzierern. Wenn Middlewares in Re-Frame beendet werden, übergeben sie das Ergebnis direkt an Event-Handler (Reduzierer). Dieses Ergebnis wird nicht als Ereignis / Aktion aufgezeichnet und kann daher nicht wiedergegeben werden. Ich würde sagen, Redux setzt stattdessen nur Hooks in Form von Aktionen dazwischen, daher glaube ich, dass ich technisch das Gleiche erreichen kann, was Re-Frame tut, mit mehr Flexibilität auf Kosten von mehr Eingabe (Erstellen von Aktionen) und wahrscheinlich mehr Raum dafür falsch.

Am Ende hindert mich nichts daran, dass Redux genau den gleichen Ansatz wie bei Re-Frame implementiert. Ich kann jede Funktion erstellen, die Reduzierer als Parameter verwendet, einige Verarbeitungsschritte ausführen und dann direkt die Reduziererfunktion mit dem Ergebnis aufrufen und sie Reduzierer-Middlewares oder was auch immer nennen. Es geht nur darum, ob ich es auf Kosten des Verlusts von DevTools und der Kopplung etwas einfacher machen möchte.

Ich muss noch mehr darüber nachdenken, ob der Re-Frame-Ansatz einen signifikanten Vorteil gegenüber einer gewissen Vereinfachung (abnehmende Anzahl von Aktionen) bietet. Ich bin offen dafür, mir das Gegenteil zu beweisen, da ich gesagt habe, ich habe es schnell durchgelesen.

Eigentlich war ich ein bisschen falsch. Der Unterschied liegt nicht in Redux- und Re-Frame-Implementierungen, sondern darin, "wo Sie Ihre Nebenwirkungen einsetzen".

Wenn Sie dies in Ereignishandlern (Reduzierern) tun und nicht zulassen, dass Aktionsersteller Ihren Kontrollfluss beeinflussen, gibt es technisch gesehen keinen Unterschied - Sie können fsm / statecharts auch in Redux verwenden und Ihren Kontrollfluss an einem Ort haben.

Aber in Wirklichkeit gibt es einen Unterschied. @gaearon hat es sehr gut erklärt: Redux erwartet, dass Ihre Reduzierungen rein sind, andernfalls wird die Funktion unterbrochen. Sie sollten das also nicht tun (Nebenwirkungen in Reduzierungen setzen), da dies gegen das Redux-Design verstößt.

Re-Frame gibt auch an, dass Ereignishandler rein sind, aber selbst in Readme wird erwähnt, dass HTTP-Anforderungen in Ereignishandlern initiiert werden. Das ist für mich etwas unklar, aber es sieht so aus, als ob ihre Erwartungen unterschiedlich sind: Sie können Ihre Nebenwirkungen in Event-Handler stecken.

Dann ist es interessant, wie sie sich der Wiedergabe nähern (und das habe ich unter https://github.com/Day8/re-frame/issues/86 gefragt).

Ich gehe davon aus, dass der einzige Weg, dies zu tun, wenn Ihre Ereignishandler Nebenwirkungen haben können, der von @ tomkis1 erwähnte ist - der von jedem Ereignishandler zurückgegebene Aufzeichnungsstatus (nicht bei jedem Ereignis Reduzierungen erneut ausführen).

Das Hot-Reload kann weiterhin funktionieren, außer dass es keine Auswirkungen auf "vergangene" Ereignisse hat - nur rechtzeitig einen neuen Zustandszweig erzeugen.

Der Unterschied im Design ist wahrscheinlich subtil, aber für mich ist es wichtig.

Ich denke, sie machen HTTP-Anfragen, indem sie reine Event-Handler mit nebenwirkenden Middlewares erstellen. Diese Komposition gibt einen neuen Event-Handler zurück, der nicht mehr rein ist, sondern der innere rein bleibt. Ich glaube nicht, dass sie vorschlagen, Event-Handler zu erstellen, die App-DB (Status) -Mutation und Nebenwirkungen mischen. Es macht Code testbarer.

Wenn Sie das Ergebnis eines Ereignishandlers aufzeichnen, können Sie ihn nicht im laufenden Betrieb wieder laden, was bedeutet, dass Sie seinen Code im laufenden Betrieb ändern können. Sie müssten das Ergebnis der Middleware aufzeichnen, und das wäre genau das gleiche, was Redux tut - zeichnen Sie dieses Ergebnis als Aktion auf und übergeben Sie es an den Reduzierer (hören Sie es sich an).

Ich denke, ich könnte sagen, dass Re-Frame-Middlewares proaktiv sind, während Redux Reaktivität zulässt (jeder Ad-hoc-Reducer kann ein Ergebnis von Middleware / AC abhören). Eine Sache, die mir langsam klar wird, ist, dass Wechselstrom, wie er hauptsächlich in Flux- und Middlewares verwendet wird, dasselbe ist wie Controller.

Wie ich verstanden habe, schlägt ausgehe, dass dies perfekte Auswirkungen haben könnte, wenn dies bei jeder einzelnen Mutation durchgeführt wird.

Ok, wenn Sie feststellen, dass Reduzierer / Ereignishandler einen völlig neuen Status zurückgeben, haben Sie dasselbe gesagt.

@merk Ich werde versuchen, meine Gedanken darüber wahrscheinlich am Montag zu schreiben, da ich wahrscheinlich an einem Wochenende nicht hier bin.

Re-Frame-Dokumente empfehlen, in Ereignishandlern mit dem Server zu sprechen: https://github.com/Day8/re-frame#talking -to-a-server

Hier weicht es also von Redux ab. Und dieser Unterschied ist tiefer als es auf den ersten Blick aussieht.

Hm, ich sehe den Unterschied zu Redux nicht:

Die auslösenden Ereignishandler sollten organisieren, dass die On-Success- oder On-Fail-Handler für diese HTTP-Anforderungen selbst einfach ein neues Ereignis auslösen . Sie sollten niemals versuchen, app-db selbst zu ändern. Das wird immer in einem Handler gemacht.

Ersetzen Sie das Ereignis durch Aktion. Redux hat nur einen speziellen Namen für einen reinen Event-Handler - einen Reducer. Ich glaube, ich vermisse etwas oder mein Gehirn ist dieses Wochenende ausgefallen.

Ich könnte einen bedeutenderen Unterschied riechen, wie Ereignisse ausgeführt werden - in der Warteschlange und asynchron. Ich denke, Redux führt Aktionen synchronisieren und Aktionen schrittweise ausführen, um die Konsistenz des Status zu gewährleisten. Dies kann die Wiederspielbarkeit weiter beeinträchtigen, da ich nicht sicher bin, ob das Speichern und wiederholte Abspielen von Ereignissen zum gleichen Endzustand führt. Ohne zu wissen, dass ich das nicht einfach tun kann, weil ich nicht weiß, welche Ereignisse Nebenwirkungen auslösen, anders als in Redux, wo Aktionen immer reinen Code auslösen.

@vladap Um den Unterschied so zusammenzufassen, wie ich ihn sehe:

1.

  • In Redux initiieren Sie eine Serveranforderung im Aktionsersteller (siehe @gaearon- Antwort auf die ursprüngliche Frage zu diesem Problem). Ihre Reduzierungen müssen rein sein
  • In Re-Frame tun Sie dies im Event-Handler (Reduzierer). Ihre Event-Handler (Reduzierer) können Nebenwirkungen haben

2.

  • Im ersten Fall wird Ihr Kontrollfluss zwischen Wechselstrom und Reduzierstück aufgeteilt.
  • Im zweiten Fall befindet sich Ihr gesamter Kontrollfluss an einem Ort - Event-Handler (Reduzierer).

3.

  • Wenn Ihr Kontrollfluss aufgeteilt ist, benötigen Sie eine Koordination zwischen Wechselstrom und Reduzierstück (auch wenn dies implizit ist): Wechselstrom liest den vom Reduzierstück erzeugten Zustand; Der Reduzierer setzt die ordnungsgemäße Reihenfolge der Ereignisse von AC voraus. Technisch gesehen führen Sie die Kopplung von Wechselstrom und Reduzierstück ein, da Änderungen am Reduzierstück auch Wechselstrom beeinflussen können und umgekehrt.
  • Wenn sich Ihr Kontrollfluss an einem Ort befindet - Sie benötigen keine zusätzliche Koordination + können Sie Tools wie FSMs oder Statecharts in Ihren Event-Handlern / Reduzierern verwenden


    1. Die Redux-Methode zur Durchsetzung reiner Reduzierungen und zur Verlagerung von Nebenwirkungen auf Wechselstromgeräte ermöglicht das Hot-Reload und die Wiedergabe

  • Die Neuformulierung von Nebenwirkungen bei Event-Handlern (Reduzierern) schließt die Tür für die Wiedergabe, wie dies bei Redux der Fall ist (durch Neubewertung von Reduzierungen), aber andere (wahrscheinlich weniger leistungsfähige) Optionen sind weiterhin möglich - wie @ tomkis1 erwähnt

Der Unterschied in der Entwicklererfahrung wird nur angezeigt, wenn Sie viele asynchrone Dinge haben (Timer, parallele http-Anforderungen, Web-Sockets usw.). Bei der Synchronisierung ist alles gleich (da in ACs kein Kontrollfluss vorhanden ist).

Ich denke, ich muss ein reales Beispiel für solch ein komplexes Szenario vorbereiten, um meinen Standpunkt klarer zu machen.

Ich könnte dich verrückt machen, das tut mir leid, aber wir sind uns nicht in einer grundlegenden Sache einig. Vielleicht sollte ich den Code für Re-Frame-Beispiele lesen, ich werde eines Tages Clojure lernen. Vielleicht hast du es getan, daher weißt du mehr. Vielen Dank für Ihre Mühe.

1.

In Redux initiieren Sie eine Serveranforderung im Aktionsersteller (siehe @gaearon- Antwort auf die ursprüngliche Frage zu diesem Problem). Ihre Reduzierungen müssen rein sein
In Re-Frame tun Sie dies im Event-Handler (Reduzierer). Ihre Event-Handler (Reduzierer) können Nebenwirkungen haben

Der von mir fett gedruckte Doc sagt ausdrücklich, dass ich einen Webapi-Aufruf in einem Ereignishandler einleiten und das Ereignis dann bei Erfolg auslösen soll. Ich verstehe nicht, wie ich dieses ausgelöste Ereignis im selben Ereignishandler behandeln kann. Dieses Ereignis bei Erfolg wird an den Router gesendet (Gleichung des Dispatchers, glaube ich), und ich benötige noch einen weiteren Ereignishandler, um es zu verarbeiten. Dieser zweite Event-Handler ist rein und entspricht dem Redux-Reduzierer, der erste dem ActionCreator. Für mich ist es dasselbe oder ich vermisse eindeutig einige wichtige Erkenntnisse.

Der Unterschied in der Entwicklererfahrung wird nur angezeigt, wenn Sie viele asynchrone Dinge haben (Timer, parallele http-Anforderungen, Web-Sockets usw.). Bei der Synchronisierung ist alles gleich (da in ACs kein Kontrollfluss vorhanden ist).

Da sind wir uns nicht einig. Asynchrone Dinge spielen eigentlich keine Rolle, Sie können sie nicht in Redux wiedergeben, daher haben Sie hier keine Entwicklererfahrung. Der Unterschied tritt auf, wenn Sie viele komplexe reine Reduktionsmittel haben. In Redux können Sie Status A, eine Aktionssequenz, einen Reduzierer, wiedergeben und den Status B abrufen. Wenn Sie alles gleich lassen, aber den Code am Reduzierer ändern und ihn im laufenden Betrieb neu laden, können Sie dieselbe Aktionssequenz aus Status A und Ihnen wiedergeben Erhalten Sie Status C und sehen Sie sofort die Auswirkungen Ihrer Codeänderung. Mit dem @ tomkis1- Ansatz ist dies nicht

Sie können sogar Testszenarien darauf erstellen. Speichern Sie einen Anfangszustand, speichern Sie eine Aktionssequenz, reduzieren Sie die Aktionssequenz auf Status A, rufen Sie Status B ab und bestätigen Sie ihn. Nehmen Sie dann Änderungen am Code vor, von denen Sie erwarten, dass sie denselben Status B ergeben. Wiederholen Sie das Testszenario, um sicherzustellen, dass es wahr ist und Ihre Änderungen Ihre App nicht beschädigt haben. Ich sage nicht, dass dies der beste Testansatz ist, aber er kann durchgeführt werden.

Eigentlich wäre ich ziemlich überrascht, wenn Clojuristen, die ich fast als eingefleischte Praktiker der funktionalen Programmierung betrachte, Haskellers empfehlen würden, Nebenwirkungen mit Code zu mischen, der zumindest ohne ein gewisses Maß an Komposition rein sein könnte.

Lazy Evaluation / Reaktivität ist die grundlegende Technik, um nebenwirkenden Code so weit wie möglich von ansonsten reinem Code zu entfernen. Durch die träge Bewertung hat Haskell schließlich ein unpraktisches Konzept der funktionalen Reinheit angewendet, um ein Programm zu erhalten, das etwas Praktisches tut, weil ein vollständig reines Programm nicht viel kann. Unter Verwendung von monadischer Komposition und anderen wird das gesamte Programm als Datenfluss erstellt. Um den letzten Schritt in der Pipeline rein zu halten und niemals Nebenwirkungen in Ihrem Code aufzurufen, gibt das Programm eine Beschreibung zurück, was zu tun ist. Es wird an die Laufzeit übergeben, die es träge ausführt - Sie lösen die Ausführung nicht durch einen imperativen Aufruf aus. Zumindest verstehe ich so die funktionale Programmierung aus der Vogelperspektive. Wir haben keine solche Laufzeit, wir simulieren sie mit ActionCreators und Reaktivität. Haftungsausschluss, nimm es nicht als selbstverständlich an, es ist das, was ich verstanden habe, wenn ich nicht so viel über FP gelesen habe, um hier irgendeine Art von Autorität zu sein. Ich könnte weg sein, aber ich glaube tatsächlich, dass ich den Punkt verstanden habe.

Ich verstehe nicht, wie ich dieses ausgelöste Ereignis im selben Ereignishandler behandeln kann.

Das sage ich nicht. Mit "Nebenwirkungen" meine ich, dass eine HTTP-Anforderung im Ereignishandler (Reduzierer) initiiert wird. Das Durchführen von E / A ist ebenfalls ein Nebeneffekt, auch wenn dies die Rückkehr Ihrer Funktion nicht beeinträchtigt. Ich meine nicht "Nebenwirkungen" in Bezug auf das Ändern des Status direkt in Erfolgs- / Fehlerbehandlungsroutinen.

Dieser zweite Event-Handler ist rein und entspricht dem Redux-Reduzierer, der erste dem ActionCreator.

Ich denke nicht, dass der erste dem Action Creator entspricht. Warum brauchen Sie sonst Action Creators, um dies überhaupt zu tun? Wenn Sie das gleiche im Reduzierstück tun können?

Mit dem @ tomkis1- Ansatz ist dies nicht

Zustimmen. Und das habe ich gemeint, als ich "andere (wahrscheinlich weniger leistungsfähige) Optionen" geschrieben habe.

Ich denke nicht, dass der erste dem Action Creator entspricht. Warum brauchen Sie sonst Action Creators, um dies überhaupt zu tun? Wenn Sie das gleiche im Reduzierstück tun können?

ActionCreators werden verwendet, um Nebenwirkungen von der reinen App zu isolieren (die wiederholt werden kann). Da Redux entwickelt wurde, können (sollten) Sie keine Nebenwirkungen bei Reduzierern ausführen. Sie benötigen ein anderes Konstrukt, in das Sie nebenwirkenden Code einfügen können. In Redux sind diese Konstrukte AC oder Middleware. Der einzige Unterschied, den ich beim Re-Frame sehe, ist, was Middleware macht und dass Re-Frame keine Middleware hat, um Ereignisse (Aktionen) zu transformieren, bevor Handler ausgelöst werden.

Anstelle von AC / Middlewares kann eigentlich alles verwendet werden - util-Modul, Dienste (wie Angular-Dienste), Sie können es als separate Bibliothek erstellen, die Sie dann entweder über AC oder Middleware verbinden und deren API in Aktionen übersetzen. Wenn Sie mich fragen, ist AC nicht der richtige Ort, um diesen Code zu platzieren. AC sollte nur aus einfachen Erstellern / Fabriken bestehen, die Aktionsobjekte aus Argumenten erstellen. Wenn ich ein Purist sein möchte, ist es besser, nebenwirkenden Code ausschließlich für Middleware zu verwenden. ActionCreator ist ein schlechter Begriff für eine Art Controller-Verantwortung. Aber wenn dieser Code nur ein einzeiliger Webapi-Aufruf ist, besteht die Tendenz, ihn einfach in AC zu setzen, und für eine einfache App kann er in Ordnung sein.

AC, Middlewares und Bibliotheken von Drittanbietern bilden eine Schicht, die ich gerne als Service-Schicht bezeichne. Ursprüngliche Facebook WebApiUtils wären Teil dieser Ebene, genauso als würden Sie den React-Router verwenden und seine Änderungen abhören und sie in Aktionen zum Aktualisieren von Redux.state übersetzen. Relay kann als Service-Schicht verwendet werden (als Redux für App / Ansicht verwenden Zustand). Da die meisten Bibliotheken von Drittanbietern derzeit programmiert sind, spielen sie bei der Wiedergabe nicht gut. Natürlich möchte ich nicht alles von Grund auf neu schreiben, also würde es so sein. Ich denke gerne darüber nach, dass diese Bibliotheken, Dienste, Dienstprogramme usw. die Browserumgebung erweitern und eine Plattform bilden, auf der ich meine wiedergabefähige App erstellen kann. Diese Plattform ist voller Nebenwirkungen, eigentlich ist es der Ort, an den ich absichtlich Nebenwirkungen verschiebe. Ich übersetze diese Plattform-API in Aktionen und meine App stellt indirekt eine Verbindung zu dieser Plattform her, indem sie diese Aktionsobjekte abhört (ich versuche, den Haskell-Ansatz zu simulieren, den ich in einem Beitrag oben beschrieben habe). Dies ist eine Art Hack in der funktionalen Programmierung, wie man immer noch Reinheit erreicht (möglicherweise nicht im absoluten akademischen Sinne), aber Nebenwirkungen auslöst.

Es gibt noch eine andere Sache, die mich verwirrt, aber ich könnte dich missverstehen. Ich denke, Sie haben gesagt, dass es für eine komplexe Logik von Vorteil ist, alles an einem Ort zu haben. Ich denke ganz im Gegenteil.

Das gesamte Aktionsgeschäft ähnelt im Konzept DOM-Ereignissen. Ich möchte meinen Geschäftscode wirklich nicht mit einem Code mischen, wie z. B. ein Mausbewegungsereignis erkennt, und ich bin sicher, dass Sie dies auch nicht tun würden. Glücklicherweise bin ich es gewohnt, über addListener ('mousemove', ...) zu arbeiten, da diese Verantwortlichkeiten gut voneinander getrennt sind. Es geht darum, diese gute Trennung der Bedenken auch für meine Geschäftslogik zu erreichen. ActionCreators, Middleware sind Werkzeuge dafür.

Stellen Sie sich vor, ich würde eine App gegen ein veraltetes Webapi schreiben, das meinen geschäftlichen Anforderungen nicht gut entspricht. Um die erforderlichen Daten zu erhalten, müsste ich einige Endpunkte aufrufen, deren Ergebnis verwenden, um die nächsten Aufrufe zu erstellen, und dann alle Ergebnisse in einer kanonischen Form zusammenführen, die ich in Redux.state verwenden würde. Diese Logik würde nicht in meine Reduzierungen eindringen, so dass ich nicht immer wieder Daten in meine Reduzierungen umwandeln muss. Es wäre in Middleware. Tatsächlich würde ich unordentliche veraltete APIs isolieren und meine Business-App so entwickeln, wie sie gegen eine nette API geschrieben würde. Sobald dies erledigt ist, würde ich vielleicht eine Änderung erhalten, um unser Webapi neu zu erstellen, und dann würde ich einfach meine Middleware neu schreiben.

Daher scheint es für eine einfache App einfach zu sein, alles an einem Ort zu haben. Aber ganz im Gegenteil, für komplexe Dinge sehe ich eine gute Trennung von Bedenken als vorteilhaft an. Aber vielleicht habe ich Ihren Standpunkt oder Anwendungsfall, auf den Sie sich beziehen, nicht verstanden.

Tatsächlich würde ich wahrscheinlich so viel Datentransformation in ihre kanonische Form auf Reduzierer verlagern und stattdessen die Reduziererzusammensetzung verwenden, wie es mir die Wiedergabe und Testbarkeit für diesen Code geben würde, der im Allgemeinen rein ist.

Ich verstehe nicht, wie ich dieses ausgelöste Ereignis im selben Ereignishandler behandeln kann.
Das sage ich nicht.

Ich dachte, Sie möchten Webapi-Aufrufe mit Reduzierungslogik mischen, um sie an einem Ort zu haben. Was ich sage, dass Sie es auch nicht im Re-Frame tun, weil sie vorschlagen, Ereignis bei Erfolg auszulösen.

Es kann vorkommen, dass webapi -> viel Logik -> webapi -> viel Logik -> ... -> für den Reduzierer die gesamte Kette durch einen einzigen Klick auslöst. In solchen Fällen wäre der größte Teil der Logik wahrscheinlich in Wechselstrom, ich würde sie nicht teilen, bis alle Nebenwirkungen beendet sind. Beziehen Sie sich darauf?

Das Beste, was ich hier tun kann, ist, so viel Logik wie möglich auf reine Funktionen zu verlagern, um sie zu testen, aber sie würden im AC-Bereich aufgerufen.

Ich glaube nicht, dass ich die Möglichkeit mag, dies als eine Reihe indirekt verbundener Ereignishandler zu tun. Es wäre ein Versprechen oder eine beobachtbare Kette.

Erfahrene Reduxer könnten unterschiedliche Meinungen zu diesem Gedanken haben ( @gaearon , @johanneslumpe , @acdlite , @emmenko ...)

@vladap Ich denke, dieses Gespräch hat sich zu weit vom Thema dieser Ausgabe entfernt. Sie haben hier bereits den Hauptpunkt meiner Kommentare erwähnt:

Daher kann ich mir vorstellen, dass es in einigen sehr komplexen Szenarien schwierig sein kann, die Logik zwischen Wechselstrom und Reduzierern synchron zu halten.

Kurzes Beispiel:

Situation 1: Sie haben irgendwo auf der Seite eine Liste der Top-10-Blog-Autoren. Jetzt haben Sie eine Kategorie von Posts im Blog gelöscht. Nach erfolgreichem Löschen müssen Sie diese Autorenliste vom Server aktualisieren, um sie auf den neuesten Stand zu bringen.

Situation 2: Sie befinden sich auf einer anderen Seite. Es gibt keine Liste der Autoren, aber eine Liste der Top-10-Kommentare. Sie können auch eine Kategorie von Blog-Posts löschen und müssen die Liste der Kommentare nach erfolgreichem Löschen aktualisieren.

Wie gehen wir also auf der Ebene der Staatsmaschine damit um? (Wir könnten auch React-Hooks als Hilfe verwenden, aber eine gute Zustands-Engine sollte in der Lage sein, selbstständig konsistent zu bleiben.)

Optionen:
A) Wir setzen diese Logik in AC. Dann berührt AC CategoryApi (zum Löschen), liest aus dem Zustand userList und commentList (um zu überprüfen, welche Listen jetzt im Zustand vorhanden sind) und spricht mit UserListApi und / oder CommentListApi (um Listen zu aktualisieren) + Versand TOP_AUTHORS_UPDATED und / oder TOP_COMMENTS_UPDATED . Es werden also grundsätzlich 3 verschiedene Domänen berührt.

B) Wir setzen es in Event-Handler userList und commentList . Diese Handler würden beide das Ereignis DELETE_CATEGORY_SUCCESS abhören, dann ihren API-Service aufrufen und wiederum das Ereignis TOP_AUTHORS_UPDATED / TOP_COMMENTS_UPDATED auslösen. Jeder Handler berührt also nur die Dienste / den Status seiner eigenen Domain.

Dieses Beispiel ist wahrscheinlich zu simpel, aber selbst auf dieser Ebene werden die Dinge mit API-Aufrufen in ACs weniger hübsch.

Der Unterschied ergibt sich aus der Tatsache, dass ActionCreators im Gegensatz zu Event-Handlern in Re-Frame proaktiv mit Geschäftslogik umgehen. Basierend auf dem DOM-Ereignis kann nur ein AC ausgeführt werden. Dieser AC der obersten Ebene kann jedoch andere ACs anrufen. Es kann separate ACs für UserListApi und CommentListApi geben, damit Domänen besser getrennt werden. Es muss jedoch immer ACs (Controller-ähnlich) vorhanden sein, die sie verkabeln. Dieser Teil ist ein ziemlich klassischer Imperativcode, während Re-Frame vollständig ereignisbasiert ist. Mit ein wenig Arbeit kann es durch einen bevorzugten Ansatz ersetzt werden, ereignisbasiert, beobachtbar, cps usw.

@vladap Es wäre interessant zu sehen, ob ein anderer Ansatz praktikabel ist: Wenn Reduzierungen Nebenwirkungen haben können, sie aber auch isolieren können, so dass sie bei der Wiedergabe ignoriert werden können.

Angenommen, die Signatur des Reduzierers würde sich ändern in: (state, action) => (state, sideEffects?) wobei sideEffects ein Abschluss ist. Abhängig vom Kontext-Framework können diese Nebeneffekte dann entweder ausgewertet oder ignoriert werden (im Falle einer Wiedergabe).

Dann würde das Beispiel aus der ursprünglichen Problembeschreibung folgendermaßen aussehen:

export default function user(state = initialState, action) {
  switch (action.type) {
    case LOGIN_ATTEMPT:
      return [state, () => {LoginApiCall.login(action.user)}];
    case LOGGED_FAILED:
      // do something
      return state;
    case LOGGED_SUCCESSFULLY:
      // do something else
     return state;
    default:
      return state;
  }
}

(alles andere bleibt ziemlich gleich)

Zumindest befindet sich Ihr Kontrollfluss an einem Ort, Ihre Nebenwirkungen sind immer einfach (da sie nur eine weitere Aktion / ein anderes Ereignis in asynchronen Handlern erzeugen und die Logik in Reduzierungen verbleibt - so wird mehr von Ihrem Code wiedergegeben).

Sie können auch regulären FSM- oder Statechart-ähnlichen Code schreiben (mit Zusammensetzung der Reduzierer / Ereignishandler).

Außerdem müssen Sie den Abschluss mit der vordefinierten Signatur in Aktionserstellern nicht zurückgeben.

Ich bin mir nicht sicher, welche Probleme dieser Ansatz möglicherweise verursachen könnte, aber für mich lohnt es sich, damit zu spielen.

Angenommen, die Signatur des Reduzierers würde sich ändern zu: (Status, Aktion) => (Status, SideEffects?), Wobei sideEffects ein Abschluss ist. Abhängig vom Kontext-Framework können diese Nebeneffekte dann entweder ausgewertet oder ignoriert werden (im Falle einer Wiedergabe).

Dies ähnelt dem, was ich von Elm mit (State, Action) => (State, Request?) . Ich habe noch keine Beispiele gesehen, aber wenn jemand dies untersuchen möchte, wenden Sie sich bitte an.

Ein weiterer Ansatz, von dem Sie sich möglicherweise inspirieren lassen können, ist die Implementierung von Eventsourcing auf der Grundlage des Actor-Modells - Akka Persistance , PersistentFSM . Ich sage nicht, dass es besser ist, aber es ist nicht schlecht, andere Versuche zu kennen. Schauspieler können Nebenwirkungen verwenden, aber wenn ich mich richtig erinnere, muss manchmal expliziter Code für die Wiedergabe geschrieben werden.

Möglicherweise verzerrt die Wiedergabefunktion unsere Entscheidungen, wo Code hingehört. Es scheint, dass wir anfangen zu denken als ... "Ich möchte, dass dies wiedergegeben werden kann, daher muss es zu einem Reduzierer gehen". Dies kann zu einem Code mit unklaren Grenzen führen, da wir ihn nicht nach seinen Verantwortlichkeiten / Rollen aufteilen und dazu neigen, unsere Geschäftslogik künstlich aufzuteilen, um ihn wiederzugeben.

Wenn ich die Wiedergabefunktion beiseite legen würde, wie würde ich meinen Geschäftscode schreiben? Ich würde versuchen, meine Geschäftslogik vor einem Aktionsprotokoll zu haben und an einem Ort zu haben. Geschäftslogik-Zustandsautomaten oder was auch immer ich verwenden würde, würden alle schwierigen Entscheidungen treffen, die zu einem bereits aufgelösten Strom von FAKTEN (Aktionen) führen würden. Reduzierer wären im Allgemeinen einfache Updater und ihre Hauptverantwortung wäre, wie die Aktionsnutzlast auf Redux.state angewendet wird. Sie könnten eine gewisse Logik haben, aber in gewissem Sinne Fakten anders interpretieren, um eine andere Art von Sicht auf Fakten- (Aktions-) Streams zu erhalten, um sie einem Benutzer zu präsentieren.

Um Verantwortlichkeiten aufzuteilen, denke ich auch gerne so darüber nach. Welcher Code könnte möglicherweise angefordert werden, um auf einem Server erneut implementiert zu werden? Fe, um langsame Geräte besser zu unterstützen, indem rechenintensive Geschäftslogik, möglicherweise einige Sicherheitsgründe, Intelligenz-Eigenschaft, was auch immer versteckt. Ich kann die Logik nicht so einfach von Reduzierern auf den Server verschieben, da sie an eine Ansichtsebene gebunden ist. Wenn komplexe Geschäftslogik vor das Aktionsprotokoll geschrieben wird, kann ich sie durch REST-Aufrufe ersetzen, die in denselben Aktionsstrom übersetzt werden, und meine Ansichtsebene sollte (wahrscheinlich) funktionieren.

Der Nachteil ist, dass ich im Gegensatz zu Akka Persistance keine Möglichkeit habe, diesen Code wiederzugeben.

Ich muss eines Tages einen genaueren Blick auf Elm werfen und verstehen, wie Ihr Vorschlag funktionieren würde.

@gaearon Danke, dass du Elm https://gist.github.com/evancz/44c90ac34f46c63a27ae - mit ähnlichen Ideen (aber viel weiter fortgeschritten).

Sie führen das Konzept von Aufgaben (http, db usw.) und Effekten (nur eine Warteschlange von Aufgaben) ein. Ihr "Reduzierer" kann also tatsächlich neue Zustände und Effekte zurückgeben.

Das Coole daran (und ich hatte es nicht so gesehen) ist, dass Sie Aufgaben sammeln können, während Sie Ihre Reduzierungen verketten - und später eine Funktion auf diese Liste anwenden. Angenommen, Sie könnten http-Anforderungen stapeln, DB-Abfragen mit Transaktionen umschließen usw.

Oder Sie könnten sagen, "Wiederholungsmanager", was nur ein Noop für Effekte ist.

@merk Ich denke, das meiste wurde bereits geschrieben. Nur dass diese Art von autonomem Code mit Nebenwirkungen wahrscheinlich am schwierigsten ist. Es wird wahrscheinlich das Chaos bei der Wiedergabe zerstören, da die Intervalle nicht mit dem Zeitverlauf synchronisiert werden, vorausgesetzt, dass der Zeitverkehr mit demselben Code ausgeführt wird und die Intervalle auch im Wiedergabemodus beginnen.

Die übliche Anwendung benötigt keinen Token- und Ablauf-Countdown, der einem Benutzer angezeigt wird. Technisch gesehen muss er sich nicht in Redux.state befinden. Admin-App muss es möglicherweise haben. Daher können Sie es in beide Richtungen implementieren, was auch immer zu Ihnen passt.

Eine Möglichkeit besteht darin, den Ablauf-Countdown in Login AC zu starten. Ich gehe davon aus, dass er endlos ausgeführt wird und mit der App stirbt oder die Abmeldung ihn bereinigen muss. Wenn das Intervall ausgelöst wird, wird das getan, was zur Bestätigung des Tokens erforderlich ist. Wenn es abgelaufen ist, wird die Aktion LOGIN_EXPIRED ausgelöst, und der Reduzierer für das Abhören löscht die Benutzersitzung und ändert den Speicherort, wodurch der Router-Übergang zu / login ausgelöst wird.

Eine andere Möglichkeit besteht darin, die lib von Drittanbietern zu verwenden und Bedenken an sie zu delegieren und ihre API in die Aktion LOGIN_EXPIRED zu übersetzen. Oder schreiben Sie Ihren eigenen und behalten Sie den Token- und Countdown-Status hier bei, wenn Sie ihn nicht in der Ansichtsebene benötigen.

Sie können es in Redux.state behalten, aber dieser Zustand ist flüchtig. Bei großen Zuständen können wir dieselben Probleme bekommen wie beim Programmieren mit globalen Variablen. Und es ist schwer, wahrscheinlich unmöglich, es zu testen. Es ist einfach, Reduzierer zu testen, aber dann beweist man, dass es funktioniert, wenn nur ein Reduzierer diesen bestimmten Schlüssel im Statusobjekt aktualisiert. Dies kann bei großen Projekten mit vielen Entwicklern unterschiedlicher Qualitäten schlecht werden. Jeder kann sich entscheiden, einen Reduzierer zu schreiben und denken, dass ein Teil des Zustands gut zu aktualisieren ist, und ich kann mir nicht vorstellen, wie alle Reduzierer zusammen in allen möglichen Aktualisierungssequenzen getestet werden sollen.

Abhängig von Ihrem Anwendungsfall kann es sinnvoll sein, diesen Status zu schützen, da es sich um eine Anmeldung handelt - eine sehr wichtige Funktion, die getrennt werden muss. Wenn dieser Status in einer Ansichtsebene benötigt wird, replizieren Sie ihn in Redux.state auf ähnliche Weise, wie der Status des React-Router-Speicherorts in einigen Beispielen in Redux.state repliziert wird. Wenn Ihr Staat und Ihr Team klein sind und sich gut benehmen, kann es in Ordnung sein, es an einem Ort zu haben.

@vladar @vladap

Wow, dieser Elm-Kern ist verdammt cool! Es erinnert mich auch an die "Treiber" -Architektur von Cycle.j .

@merk Tatsächlich würde autonomes LOGIN_EXPIRED die Wiedergabe nicht stark beeinflussen, da es am Ende des Aktionsprotokolls landen würde und nicht sofort von einer Wiederholung verarbeitet würde und möglicherweise überhaupt nicht zum Aktionsprotokoll gelangen würde - ich weiß nicht wie genau die Wiedergabe implementiert ist.

@gaeron Es scheint, dass Elm and Cycle auf hoher Ebene das Muster implementiert, das ich beschreiben wollte, wenn ich es richtig verstehe. Ich habe einen Browser, der mir eine Basisplattform bietet. Ich erweitere diese Plattform mit meiner Service-Schicht (http, db ...), um eine Plattform für meine App zu erstellen. Dann brauche ich eine Art Klebstoff, der mit der Service-Schicht interagiert und es meiner reinen App ermöglicht, indirekt mit ihr zu kommunizieren, damit meine App beschreiben kann, was sie tun möchte, es aber nicht selbst ausführt (Nachrichten an Cycle-Treiber, Elm Effects) eine Liste von Aufgaben haben). Ich könnte sagen, meine App erstellt ein "Programm" unter Verwendung von Datenstrukturen (erinnert mich an Code als Datenmantra), das dann zur Ausführung an die Serviceschicht übergeben wird, und meine App ist nur an einem Ergebnis dieses "Programms" interessiert, das sie dann anwendet zu seinem Zustand.

@merk : Weiter. Wenn das Token und sein Ablauf in Redux.state / oder irgendwo in einem anderen js-Dienst abgelegt werden, wird es nicht übertragen, wenn der Benutzer die App in einem neuen Tab öffnet. Ich würde sagen, der Benutzer erwartet, dass er angemeldet bleibt. Ich gehe also davon aus, dass sich dieser Status tatsächlich im SessionStore befinden sollte.

Auch ohne dies können Zustände getrennt werden, wenn das Ablaufen des Tokens nicht die sofortige Bereinigung der Benutzerinformationen und den Übergang zu / login bedeutet. Bis der Benutzer den Server mit seinen Interaktionen nicht berührt (er hat genügend zwischengespeicherte Daten), kann er weiterarbeiten und die Aktion LOGIN_EXPIRED wird nur ausgelöst, wenn er etwas tut, das einen Server erfordert. Der auf Redux.state basierende isLogged-Status muss nicht bedeuten, dass ein gültiges Token vorhanden ist. Der isLogged-Status in Redux.state wird verwendet, um zu entscheiden, was gerendert werden soll. Der Status um ein Token kann für eine Ansichtsebene ausgeblendet und ausschließlich auf der Ebene der Aktionsersteller verwaltet werden, ohne dass Aktionen und Reduzierungen dafür geschrieben werden müssen, mit Ausnahme derjenigen, die eine Ansichtsebene betreffen.

@vladar Ich denke, ich könnte Ihren allgemeinen Punkt bekommen. Ich glaube, ich habe es noch nie bemerkt.

Es gibt keine einfache Möglichkeit, die Kontrolle vom Reduzierer an den Aktionsersteller zurückzugeben und einige Werte zu übergeben. Nehmen wir an, diese Werte werden nicht zum Rendern benötigt, sie sind sogar temporär, aber sie werden benötigt, um eine asynchrone Operation zu initiieren.

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
}

reducer() {
  case ACTION1
    ... some logic L2
    ... x, y result from L2
    ... some logic L3
    ... resulting in new state
    ... return new state
}

Es gibt drei Möglichkeiten, um eine asynchrone Operation mit x, y zu initiieren, die im Reduzierer berechnet wurden. Keiner von ihnen ist perfekt.

1) Verschieben Sie die Logik vom Reduzierer zum Aktionsersteller und verringern Sie die Leistung beim Hot-Reload. Der Reduzierer ist nur ein dummer Zustandsaktualisierer oder hat die Logik L3.

2) Speichern Sie x, y in Redux.state, verschmutzen Sie es mit temporären / transienten Werten und verwenden Sie es im Grunde genommen als globalen Kommunikationskanal zwischen Reduzierern und Aktionserstellern, von dem ich nicht überzeugt bin, dass ich es mag. Ich denke, es ist in Ordnung, wenn es sich um einen tatsächlichen Zustand handelt, aber nicht für diese Art von Werten. Der Aktionsersteller ändert sich zu:

actionCreator() {
  ... getState
  ... some logic L1
  ... dispatch({ACTION1})
  // I assume I get updated state if previous dispatch is sync.
  // If previous dispatch is async it has to be chained properly.
  // If updated state can't be received here after a dispatch
  // then I would think there is a flaw.
  ... getState
  ... asyncOperation(state.x, state.y)
}

3) Speichern Sie x, y, um anzugeben, verwenden Sie es als Requisiten in einer Komponente und tunneln Sie es mit componentWillReceiveProps durch die gesamte Schleife der Ansichtsebene, wodurch ein neuer Aktionsersteller und die asynchrone Operation ausgelöst werden. Die schlechteste Option, wenn Sie mich fragen, die Geschäftslogik überall zu verbreiten.

@vladar
Im Allgemeinen spielt es keine Rolle, wo die Asynchronisierung eingeleitet wird, sofern das Ergebnis dieser Operation als Aktion gepackt, von einem Reduzierer ausgelöst und angewendet wird. Es wird genauso funktionieren. Es ist die gleiche Diskussion wie bei Vanilla Flux, ob Async in Aktionserstellern oder in Geschäften eingeleitet werden soll.

Es würde erfordern, dass Redux diese asynchronen Aufrufe bei der Wiedergabe identifizieren und ignorieren kann, was genau das ist, was Sie vorschlagen.

Es gibt keine einfache Möglichkeit, die Kontrolle vom Reduzierer an den Aktionsersteller zurückzugeben und einige Werte zu übergeben.

Ich würde sagen, dies ist eines der Symptome. Ihr zweites Beispiel veranschaulicht meinen Standpunkt sehr gut (siehe unten).

Betrachten Sie den allgemeinen Fluss des Zustandsübergangs (in der FSM-Welt):

  1. Ereignis kommt an
  2. Bei aktuellem Status berechnet FSM den neuen Zielstatus
  3. FSM führt Operationen aus, um vom aktuellen Zustand in den neuen Zustand überzugehen

Beachten Sie, dass der aktuelle Status wichtig ist! Das gleiche Ereignis kann je nach aktuellem Status zu unterschiedlichen Ergebnissen führen. Und als Ergebnis verschiedener Abfolgen von Operationen (oder Argumenten) in Schritt 3.

So funktioniert der Statusübergang in Flux, wenn Ihre Aktion synchronisiert ist. Ihre Reduzierungen / Geschäfte sind in der Tat FSMs.

Aber was ist mit asynchronen Aktionen? Die meisten Flux-Beispiele mit asynchronen Aktionen zwingen Sie zu der Annahme, dass sie invertiert sind:

  1. AC führt einen asynchronen Betrieb aus (unabhängig vom aktuellen Status)
  2. AC löst als Ergebnis dieser Operation eine Aktion aus
  3. Reducer / Store kümmert sich darum und ändert den Status

Wenn der aktuelle Status ignoriert wird, wird angenommen, dass der Zielstatus immer der gleiche ist. Während in Wirklichkeit ein konsistenter Zustandsübergangsfluss immer noch der gleiche ist. Und es muss so aussehen (mit ACs):

  1. AC erhält den aktuellen Status, bevor die Aktion ausgelöst wird
  2. AC sendet Aktion
  3. AC liest nach der Aktion einen neuen Status
  4. Gegebener Status vor und nach der Aktion - entscheidet, welche Operationen ausgeführt werden sollen (oder welche Werte als Argumente übergeben werden sollen).

Genau Ihr zweites Beispiel. Ich sage nicht, dass all diese Schritte ständig erforderlich sind. Oft kann man einige davon weglassen. Aber in komplexen Fällen müssen Sie so handeln. Es ist ein verallgemeinerter Fluss eines Zustandsübergangs. Dies bedeutet auch, dass die Grenzen Ihres FSM auf AC gegenüber Reduzierer / Speicher verschoben wurden.

Das Verschieben von Grenzen verursacht jedoch andere Probleme:

  1. Wenn sich asynchrone Vorgänge im Speicher / Reduzierer befinden, können zwei unabhängige FSMs auf dasselbe Ereignis reagieren. Um eine neue Funktion hinzuzufügen, können Sie einfach einen Speicher / Reduzierer hinzufügen, der auf ein vorhandenes Ereignis reagiert, und das ist alles.
    Bei Vorgängen in AC müssen Sie jedoch Store / Reducer hinzufügen und vorhandene ACs bearbeiten, indem Sie dort auch einen asynchronen Teil der Logik hinzufügen. Wenn es bereits eine asynchrone Logik für einen anderen Reduzierer enthält ... wird es nicht cool.
    Die Pflege eines solchen Codes ist offensichtlich schwieriger.
  2. Der Kontrollfluss Ihrer App unterscheidet sich bei Synchronisierungs- und Asynchronisierungsaktionen. Bei Synchronisierungsaktionen kontrolliert der Reduzierer den Übergang, bei Async - AC kontrolliert er effektiv alle Übergänge, die möglicherweise durch dieses Ereignis / diese Aktion verursacht werden könnten.

Beachten Sie, dass die meisten Probleme durch einfache Statusanforderungen der meisten Web-Apps verborgen sind. Aber wenn Sie eine komplexe Stateful-App haben, werden Sie mit diesen Situationen definitiv konfrontiert sein.

In den meisten Fällen werden Sie wahrscheinlich alle möglichen Problemumgehungen finden. Sie könnten sie jedoch in erster Linie vermeiden, wenn Ihre Zustandsübergangslogik besser gekapselt wäre und Ihre FSMs nicht zwischen ACs und Reduzierern getrennt wären.

Leider ist der "nebeneffektive" Teil des Zustandsübergangs immer noch ein Teil des Zustandsübergangs und kein eigenständiges Stück Logik. Deshalb sieht (state, action) => (state, sideEffects) für mich natürlicher aus. Ja, das ist keine "Reduzieren" -Signatur mehr%) Aber die Domäne des Frameworks sind keine Datentransformationen, sondern Zustandsübergänge.

Es ist die gleiche Diskussion wie bei Vanilla Flux, ob Async in Aktionserstellern oder in Geschäften eingeleitet werden soll.

Ja, aber Flux verbietet Ihnen nicht, asynchrone Inhalte in Geschäften zu haben, solange Sie () in asynchronen Rückrufen gegen den Mutationsstatus direkt versenden. Sie sind selbst dann nicht einschätzend, wenn die meisten Implementierungen die Verwendung von ACs für die Asynchronisierung empfehlen. Persönlich denke ich, dass es eine Reminiszenz an MVC ist, weil es mental bequem ist, ACs als Controller zu behandeln, anstatt sich mental auf FSMs zu verlagern.

@vladar

(state, action) => (state, sideEffects)

Ich denke nur nach. Dies bedeutet, dass wir den Rückgabetyp der Funktion combinReducers in [state, SideEffects] oder [state, [SideEffects]] ändern und den Code ändern müssen, um SideEffects aus Reduzierern zu kombinieren. Der ursprüngliche (Status, Aktion) => Statusreduzierungstyp könnte weiterhin unterstützt werden, da kombinierenReduzierer den (Status-) Rückgabetyp auf [Status] oder [Status, []] zwingen würden.

Dann müssen wir SideEffects irgendwo in Redux ausführen und versenden. Dies könnte in Store.dispatch () erfolgen, wodurch ein neuer Status angewendet und die SideEffects-Liste ausgeführt und versendet wird. Ich denke, ob wir in eine schöne endlose Rekursion geraten können.

Alternativ könnte es irgendwo auf der Middleware-Ebene ausgeführt werden, aber dann müsste Store.dispatch meiner Meinung nach die SideEffects-Liste zusammen mit einer Aktion zurückgeben.

@vladar

Ja, aber Flux verbietet Ihnen nicht, asynchrone Inhalte in Geschäften zu haben, solange Sie () in asynchronen Rückrufen gegen den Mutationsstatus direkt versenden. Sie sind selbst dann nicht einschätzend, wenn die meisten Implementierungen die Verwendung von ACs für die Asynchronisierung empfehlen. Persönlich denke ich, dass es eine Reminiszenz an MVC ist, weil es mental bequem ist, ACs als Controller zu behandeln, anstatt sich mental auf FSMs zu verlagern.

Ich bin damit einverstanden, dass die Behandlung von ActionCreators als Controller keine gute Einstellung ist. Dies ist der Grund, warum ich am Ende denke, dass ich für eine komplexe Logik die Steuerung vom Reduzierstück zurück zum Wechselstrom zurückgeben muss. Das sollte ich nicht brauchen. Wenn AC asynchron behandelt, sollten sie höchstens Anforderungshandlern ähneln - die Ausführung basiert auf einer Entscheidung, die an einer anderen Stelle getroffen wurde, und diese Anforderung sollte keine domänenübergreifenden Domänen sein.

Um ihre Rolle als Controller zu vermeiden, besteht die einzige Möglichkeit darin, die Logik durch intelligente Komponenten zu leiten und ActionCreators nur zu verwenden, um den ersten Schritt einer Logik in einer direkten Antwort auf die Benutzeroberfläche auszuführen. Die eigentliche erste Entscheidung wird in einer intelligenten Komponente getroffen, die auf Redux.state und / oder dem lokalen Status der Komponente basiert.

Dieser Ansatz, wie in Ihrem vorherigen Beispiel zum Löschen von Kategorien und zum Aktualisieren / Löschen von etwas anderem als Antwort mit Webapi, wäre nicht schön. Ich gehe davon aus, dass dies zu einer Mischung verschiedener Techniken führen wird, die auf den Entwicklungspräferenzen basieren - sollte es in AC, in Reduzierer, in intelligenten Komponenten sein? Mal sehen, wie sich Muster entwickeln werden.

Ich kaufte den Punkt, um asynchrone Anrufe von Geschäften fernzuhalten. Aber ich denke nicht, dass es gültig ist, weil es die Komplexität woanders deutlich erhöht. Andererseits sehe ich nicht viel Komplexität darin, eine asynchrone Funktion in einem Geschäft / Reduzierer zu haben, soweit sie nur dort ausgeführt und versendet wird - oder einen reinen Ansatz, diese Funktionen als Daten zu behandeln und sie irgendwo weit weg auszulösen.

Akteure tun dies auf ähnliche Weise. Sie können asynchron ausführen und eine Nachricht (Aktionsobjekt) an einen anderen Akteur senden oder sie an sich selbst senden, um in ihrem FSM fortzufahren.

Ich schließe, weil hier nichts umsetzbar zu sein scheint. Das Dokument "Asynchrone Aktionen" sollte die meisten Fragen beantworten. Weitere theoretische Diskussionen scheinen mir ohne den tatsächlichen Code, den wir ausführen könnten, nicht wertvoll zu sein. ;-);

@gaearon Können Sie einem der Beispiele mit Redux (außerhalb des "realen" Beispiels, das Middleware verwendet) tatsächlichen Code hinzufügen, der die richtige Art der Verarbeitung von API-Aufrufen implementiert?

Nach dem Lesen aller Dokumente habe ich immer noch Fragen zur tatsächlichen Platzierung. Bei diesem Problem habe ich einige Probleme, zu verstehen, in welchen Dateien diese Nebenwirkungen platziert werden sollen. Https://github.com/rackt/redux/issues/291#issuecomment -123010379 . Ich denke, dies ist das "richtige" Beispiel aus diesem Issue-Thread.

Vielen Dank im Voraus für jede Klarstellung in diesem Bereich.

Ich möchte auf einen wichtigen Gedanken über unreine Action-Schöpfer hinweisen:

Das Einfügen von asynchronen Aufrufen in die Aktionsersteller oder das Vorhandensein unreiner Aktionsersteller im Allgemeinen hat für mich einen großen Nachteil: Es fragmentiert die Steuerlogik so, dass das vollständige Auslagern der Steuerlogik Ihrer Anwendung nicht so einfach ist wie das Auslagern Ihres Geschäfts Schöpfer. Ich beabsichtige, Middleware immer für asynchrone Prozesse zu verwenden und unreine Aktionsersteller vollständig zu vermeiden.

Die App, die ich entwickle, wird mindestens zwei Versionen haben: eine, die in einem Raspberry Pi vor Ort ausgeführt wird, und eine andere, die ein Portal ausführt. Es kann sogar separate Versionen für ein öffentliches Portal / Portal geben, das von einem Kunden betrieben wird. Ob ich verschiedene APIs verwenden muss, ist ungewiss, aber ich möchte mich so gut wie möglich auf diese Möglichkeit vorbereiten, und Middleware ermöglicht es mir, dies zu tun.

Und für mich widerspricht das Konzept, API-Aufrufe unter Aktionserstellern zu verteilen, völlig dem Redux-Konzept der zentralisierten Kontrolle über Statusaktualisierungen. Sicher, die bloße Tatsache, dass eine API-Anforderung im Hintergrund ausgeführt wird, ist nicht Teil des Status des Redux-Stores - aber es ist immer noch der Anwendungsstatus, über den Sie eine genaue Kontrolle haben möchten. Und ich finde, dass ich die genaueste Kontrolle darüber haben kann, wenn, wie bei Redux-Filialen / Reduzierern, diese Kontrolle eher zentral als verteilt ist.

@ jedwards1211 Sie könnten an https://github.com/redux-effects/redux-effects interessiert sein, falls Sie sie noch nicht ausgecheckt haben, sowie an einer Diskussion unter # 569.

@gaearon cool, danke für den Hinweis! Auf jeden Fall war die Verwendung meiner eigenen Middleware bisher nicht allzu schwierig :)

Ich habe einen Ulmen-ähnlichen Ansatz entwickelt: Redux-Nebeneffekt . Die README erklärt den Ansatz und vergleicht ihn mit Alternativen.

@gregwebs Ich mag redux-side-effect , obwohl es auch mit austauschbarer Steuerlogik umständlich wäre, weil es die Frage gibt, wie ich die sideEffect -Funktion in meine Reduzierungsmodule bekomme, wenn es mehrere verschiedene Store-Konfiguratoren gibt Module zur Verwendung in verschiedenen Builds.

Ich könnte allerdings einen lustigen Hack gebrauchen: Speichern Sie einfach sideEffect im state selbst, damit es dem Reduzierer automatisch zur Verfügung steht! : stick_out_tongue_winking_eye:

So etwas wie https://github.com/rackt/redux/pull/569/ ist nahezu ideal für meine Arbeitsweise, obwohl ich es natürlich nicht in einem Produktionsprojekt verwenden möchte, es sei denn, es wird zum Standardteil der API.

Hier ist eine Idee: Lassen Sie eine Middleware eine sideEffect -Funktion auf die Aktion kleben. Etwas hackig, aber die kleinstmögliche Änderung, die erforderlich ist, damit Reduzierer asynchronen Code initiieren können:

sideEffectMiddleware.js :

export default store => next => action => {
  let sideEffects = [];
  action.sideEffect = callback => sideEffects.push(callback);
  let result = next(action);
  sideEffects.forEach(sideEffect => sideEffect(store));
  return result;
};

Es gibt mehrere Möglichkeiten für den einfachen sideEffect-Ansatz. Bitte probieren Sie Ihre Varianten aus und melden Sie sich zurück

Ich habe meinen Code überarbeitet, um den oben beschriebenen sideEffectMiddleware -Ansatz zu verwenden, und ich mag die resultierende Code-Organisation wirklich. Es ist schön, die sideEffect -Funktion nicht von irgendwo in meine Reduzierungen importieren zu müssen, sondern nur in action .

@ jedwards1211 Ich habe Ihren Code als actionSideEffectMiddleware in Version 2.1.0 des Pakets veröffentlicht.

@Gregwebs cool! Möchten Sie mich als Mitarbeiter auflisten?

@ jedwards1211 Sie werden hinzugefügt. Vielleicht fügst du die richtige Wiedergabeunterstützung hinzu :) Ich kann das noch nicht nutzen, wo ich dies benutze.

@gaearon Ich

Sie tun es, es ist nur so, dass sie zum Zeitpunkt der Aufnahme bereits einfache Objekte sind, sodass Middleware normalerweise nicht eingreift.

@gaearon Hmmm. Ich habe diese Funktion also noch nicht ausprobiert, aber ... Ich vermute, dass einige Nebeneffekt-Middlewares auch den Rekord / die Wiedergabe brechen würden? Nehmen wir zum Beispiel redux-effects-fetch , es würde immer noch eine Ajax-Anfrage für eine einfache Objekt-FETCH-Aktion ausführen:

function fetchMiddleware ({dispatch, getState}) {
  return next => action =>
    action.type === 'FETCH'
      ? fetch(action.payload.url, action.payload.params).then(checkStatus).then(deserialize, deserialize)
      : next(action)
}

Wenn der Rekorder die [success, failure] Rückrufe aus den steps der FETCH-Aktion entfernt, würde die redux-effects Middleware keine zustandsverändernden Aktionen auslösen, aber dennoch würde man nicht wollen Wiedergabe, um eine Reihe von Ajax-Anforderungen auszulösen.

Gibt es eine Möglichkeit, die "reine" Middleware (die niemals zusätzliche Aktionen auslöst) wie redux-logger von der "unreinen" Middleware (die möglicherweise Aktionen auslöst) zu trennen?

Leider habe ich redux-effects genau untersucht, sodass ich Ihre Frage nicht beantworten kann.

Kein Problem, fügt der Rekorder auf jeden Fall Flags zu den Aktionen hinzu, mit denen die Middleware entscheiden kann, ob Nebenwirkungen auftreten sollen oder nicht? Ich versuche nur, einen Weg in meiner eigenen Middleware zu finden, um die devtools korrekt unterstützen zu können

Das tut es nicht. Es wird erwartet, dass devTools() _nach_ applyMiddleware in die Enhancer-Kompositionskette eingefügt wird, sodass es sich bei Erreichen der Aktion um eine „endgültige“ Aktion handelt, die keiner weiteren Interpretation bedarf.

Oh, ich verstehe, nachdem eine Middleware einen Nebeneffekt ausgeführt hat, kann sie einfach alle Felder aus der Aktion entfernen / optimieren, damit sie den Nebeneffekt nicht auslöst, wenn sie bei der Wiedergabe durch die Middleware zurückkehrt, und sollte es dann tun arbeite mit den dev tools, oder?

Ja, das scheint ein guter Weg zu sein.

Großartig, danke, dass du mich aufgeklärt hast!

@gaearon @vladar @ jedwards1211 Sie könnten an https://github.com/salsita/redux-side-effects interessiert sein (state, action) => (state, sideEffects) aber anstelle des Reduzierers, der ein Tupel zurückgibt (was Sie bereits bemerkt haben, ist nicht möglich), ergeben sich Effekte.
Ich habe kurz damit gespielt, Hot Reload und Replay scheinen in Ordnung zu sein. Ich sehe bisher keine defekte Redux-Funktion, aber vielleicht finden einige der älteren Reduxer etwas. :) :)
Für mich ist dies eine wirklich wichtige Ergänzung zum Redux-Stack, da die Logik hauptsächlich in Reduzierungen enthalten ist, was sie zu effektiven und testbaren Zustandsautomaten macht, die Effekte (Übergangsaktionen, deren Ergebnis nicht nur vom aktuellen Status abhängt) an externe Dienste delegieren ( ziemlich ähnlich wie Elm Architektur, Effekte und Dienstleistungen).

Ein anderer Ansatz ist die Redux-Saga, bei der ebenfalls Generatoren verwendet werden, die Nebenwirkungen jedoch von den Reduzierern getrennt bleiben.

@gaearon Ich glaube, @minedeljkovic meinte das

Für mich ist dies eine wirklich wichtige Ergänzung zum Redux-Stack, da die Logik hauptsächlich in Reduzierungen enthalten ist, was sie zu effektiven und testbaren Zustandsmaschinen macht, die Effekte (Übergangsaktionen, deren Ergebnis nicht nur vom aktuellen Status abhängt) an externe Dienste delegieren ( ziemlich ähnlich wie Elm Architektur, Effekte und Dienstleistungen).

Dies ist der Hauptvorteil gegenüber dem herkömmlichen Ansatz, da redux-thunk sehr ähnlich ist, anstatt etwas Syntaxzucker darüber, was Transaktionen mit langer Laufzeit erleichtert .

https://github.com/salsita/redux-side-effects ähnelt eher der Elm-Architektur.

Ja, Redux-Saga und Redux-Nebenwirkungen verwenden Generatoren nur, um Nebenwirkungen zu deklarieren, wobei Saga bzw. Reduzierer rein bleiben. Das ist ähnlich.

Zwei Gründe, warum ich das bei Reduzierstücken bevorzuge:

  1. Sie haben expliziten Zugriff auf den aktuellen Status, der sich darauf auswirken kann, wie der Effekt deklariert werden soll (ich denke, das war einer der Punkte von @vladar während dieser Diskussion).
  2. Es wurde kein neues Konzept eingeführt (Saga in Redux-Saga)

Mein Kommentar in https://github.com/rackt/redux/issues/1139#issuecomment -165419770 ist weiterhin gültig, da redux-saga nicht lösen kann und es keine andere Möglichkeit gibt, dies zu lösen, als das Modell zu akzeptieren in der Ulmenarchitektur verwendet.

Ich habe eine einfache Zusammenfassung gegeben, die versucht, meine Punkte zu Redux-Nebenwirkungen hervorzuheben: https://gist.github.com/minedeljkovic/7347c0b528110889aa50

Ich habe versucht, ein möglichst einfaches Szenario zu definieren, das meiner Meinung nach nur "reale Welt" genug ist, aber dennoch einige der Punkte aus dieser Diskussion hervorhebt.

Szenario ist:
Die Anwendung hat den Teil "Benutzerregistrierung", in den persönliche Benutzerdaten eingegeben werden sollen. Unter anderen personenbezogenen Daten wird das Geburtsland aus der Liste der Länder ausgewählt. Die Auswahl erfolgt im Dialogfeld "Länderauswahl", in dem der Benutzer ein Land auswählt und die Auswahl hat, eine Auswahl zu bestätigen oder abzubrechen. Wenn der Benutzer versucht, die Auswahl zu bestätigen, aber kein Land ausgewählt ist, sollte er gewarnt werden.

Architekturbeschränkungen:

  1. Die CountrySelection-Funktionalität sollte modular sein (im Sinne von Redux-Enten ), damit sie in mehreren Teilen der Anwendung wiederverwendet werden kann (z. B. auch im Teil der Anwendung "Produktverwaltung", in dem das Produktionsland eingegeben werden sollte).
  2. Der Statusabschnitt von CountrySelection sollte nicht als global betrachtet werden (um die Wurzel des Redux-Status zu sein), sondern sollte lokal für das Modul sein (und von diesem Modul gesteuert werden), das es aufruft.
  3. Die Kernlogik dieser Funktionalität muss so wenig bewegliche Teile wie möglich enthalten (meiner Meinung nach ist diese Kernlogik nur in Reduzierern implementiert (und nur im wichtigsten Teil der Logik). Komponenten sollten trivial sein, wie alle reduxgetriebenen Komponenten Sein :) . Ihre einzige Verantwortung wäre es, den aktuellen Status wiederzugeben und Aktionen auszulösen.)

Der wichtigste Teil dieser Konversation ist die Art und Weise, wie countrySelection Reducer die Aktion CONFIRM_SELECTION verarbeitet.

@gaearon , ich würde Ihre Meinung zu meiner Aussage, dass Redux-Nebenwirkungen als Ergänzung zu Standard-Redux eine Oberfläche für die einfachste Lösung unter Berücksichtigung von Einschränkungen bieten, größtenteils begrüßen.

Eine mögliche alternative Idee für die Implementierung dieses Szenarios (ohne Redux-Nebenwirkungen, aber mit Redux-Saga, Redux-Thunk oder einer anderen Methode) wäre ebenfalls sehr zu schätzen.

@ tomkis1 , ich würde gerne hier deine Meinung

(In diesem Kern gibt es eine etwas andere Implementierung: https://gist.github.com/minedeljkovic/9d4495c7ac5203def688, bei der globalActions vermieden wird. Es ist für dieses Thema nicht wichtig, aber vielleicht möchte jemand dieses Muster kommentieren.)

@minedeljkovic Danke für das Wesentliche. Ich sehe ein konzeptionelles Problem mit Ihrem Beispiel. redux-side-effects soll nur für Nebenwirkungen verwendet werden. In Ihrem Beispiel gibt es jedoch keine Nebenwirkungen, sondern eine langlebige Geschäftstransaktion, und daher ist redux-saga viel besser geeignet. @slorber und @yelouafi können mehr Licht ins Dunkel bringen.

Mit anderen Worten, das für mich am meisten besorgniserregende Problem ist die synchrone dispatching der neuen Aktion innerhalb des Reduzierers (https://github.com/salsita/redux-side-effects/pull/9#issuecomment-165475991):

yield dispatch => dispatch({type: COUNTRY_SELECTION_SUCCESS, payload: state.selectedCountryId});

Ich glaube, @slorber kam mit dem Begriff "geschäftlicher Nebeneffekt" und genau das ist Ihr Fall. redux-saga glänzt bei der Lösung dieses speziellen Problems.

@mindjuice Ich bin mir nicht ganz sicher, ob ich Ihr Beispiel verstehe, aber ich mag das Onboarding-Beispiel, das ich hier gegeben habe: https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment -162822909

Das Saga-Muster erlaubt es auch, einige implizite Dinge explizit zu machen.
Zum Beispiel beschreiben Sie einfach, was durch das Auslösen von Aktionen passiert ist (ich bevorzuge Ereignisse, weil sie für mich in der Vergangenheitsform liegen sollten), und dann werden einige Geschäftsregeln aktiviert und die Benutzeroberfläche aktualisiert. In Ihrem Fall ist die Anzeige des Fehlers implizit. Wenn bei einer Saga bei Bestätigung kein Land ausgewählt ist, würden Sie wahrscheinlich eine Aktion "NO_COUNTRY_SELECTED_ERROR_DISPLAYED" oder ähnliches auslösen. Ich ziehe es vor, dass das völlig explizit ist.

Sie konnten die Saga auch als Kopplungspunkt zwischen Enten sehen.
Zum Beispiel haben Sie duck1 und duck2 mit jeweils lokalen Aktionen. Wenn Ihnen die Idee, die beiden Enten zu koppeln, nicht gefällt (ich meine, eine Ente würde die ActionsCreators der zweiten Ente verwenden), können Sie sie einfach beschreiben lassen, was passiert ist, und dann eine Saga erstellen, die einige komplexe Geschäftsregeln zwischen den Enten verkabelt 2 Enten.

Sooooo, es ist ein unglaublich langer Thread und gibt es noch eine Lösung für das Problem?

Angenommen, Sie haben ein asynchrones action() und Ihr Code muss entweder einen Fehler anzeigen oder das Ergebnis anzeigen.

Der erste Ansatz war, es so zu machen

// the action call
action().then(dispatch(SUCCESS)).catch(dispatch(FAILURE))

// the reducer
case SUCCESS:
    state.succeeded = true
    alert('Success')

case FAILURE:
    state.succeeded = false
    alert('Failure')

Aber es stellt sich heraus, dass es kein Redux-Weg ist, weil der Reduzierer jetzt "Nebenwirkungen" enthält (ich weiß nicht, was das bedeuten soll).
Kurz gesagt, der richtige Weg ist, diese alert() s irgendwo aus dem Reduzierer zu entfernen.

Das könnte irgendwo die React-Komponente sein, die dies action nennt.
Jetzt sieht mein Code so aus:

// the reducer
case SUCCESS:
    state.succeeded = true

case FAILURE:
    state.succeeded = false

// the React component
on_click: function()
{
    action().then
    ({
        dispatch(SUCCESS)

        alert('Success')
        // do something else. maybe call another action
    })
    .catch
    ({
        dispatch(FAILURE)

        alert('Failure')
        // do something else. maybe call another action
    })
}

Ist das jetzt der richtige Weg?
Oder muss ich mehr nachforschen und eine Bibliothek von Drittanbietern anwenden?

Redux ist überhaupt nicht einfach. Bei realen Apps ist das nicht leicht zu verstehen. Mabye, ein Beispiel für ein reales App-Repo, wird als kanonisches Beispiel benötigt, das den richtigen Weg veranschaulicht.

@ halt-hammerzeit Redux selbst es sehr einfach; Die verwirrende Fragmentierung beruht auf unterschiedlichen Bedürfnissen oder Meinungen zur Integration / Trennung von Nebenwirkungen von Reduktionsmitteln bei der Verwendung von Redux.

Die Fragmentierung kommt auch von der Tatsache, dass es wirklich nicht so schwer ist, Nebenwirkungen zu reduzieren, wie Sie es mit Redux wollen. Ich brauchte nur etwa 10 Codezeilen, um meine eigene grundlegende Middleware für Nebenwirkungen zu erstellen. Umfassen Sie also Ihre Freiheit und sorgen Sie sich nicht um den "richtigen" Weg.

@ jedwards1211 "Redux selbst" hat keinen Wert oder keine Bedeutung und macht keinen Sinn, da sein Hauptzweck darin besteht, die alltäglichen Probleme von Flux-Entwicklern zu lösen. Das impliziert AJAX, wunderschöne Animationen und all die anderen normalen und erwarteten Dinge

@ halt-hammerzeit hast du da einen punkt. Redux scheint sicherlich dazu gedacht zu sein, die meisten Leute dazu zu bringen, sich auf die Verwaltung von Statusaktualisierungen zu einigen. Es ist also eine Schande, dass die meisten Leute sich nicht darauf geeinigt haben, wie Nebenwirkungen durchgeführt werden sollen.

Haben Sie den Ordner "examples" im Repo gesehen?

Obwohl es mehrere Möglichkeiten gibt, Nebenwirkungen auszuführen, wird im Allgemeinen empfohlen, dies entweder außerhalb von Redux oder innerhalb von Aktionserstellern zu tun, wie wir es in jedem Beispiel tun.

Ja, ich weiß ... alles was ich sage ist, dass dieser Thread einen Mangel an Konsens (zumindest unter den Leuten hier) über den besten Weg zur Durchführung von Nebenwirkungen gezeigt hat.

Obwohl vielleicht die meisten Benutzer Thunk-Action-Ersteller verwenden und wir nur Ausreißer sind.

^ was er gesagt hat.

Ich würde es vorziehen, wenn sich alle größten Köpfe auf eine einzige gerechte Lösung einigen würden
und schnitzen Sie es in Stein, damit ich nicht durch 9000 Bildschirme lesen muss
Faden

Am Donnerstag, dem 7. Januar 2016, schrieb Andy Edwards [email protected] :

Ja, ich weiß ... alles was ich sage ist, dass dieser Thread einen Mangel an illustriert hat
Konsens (zumindest unter den Menschen hier) über die beste Art, Seite zu leisten
Auswirkungen.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

Ich denke, die derzeit vereinbarte Lösung besteht darin, alles in Aktion zu tun
Schöpfer. Ich werde versuchen, diesen Ansatz zu verwenden, falls ich Fehler finde
es werde ich wieder hier posten

Am Donnerstag, dem 7. Januar 2016, schrieb Николай Кучумов [email protected] :

^ was er gesagt hat.

Ich würde es vorziehen, wenn sich alle größten Köpfe auf eine einzige gerechte Lösung einigen würden
und schnitzen Sie es in Stein, damit ich nicht durch 9000 Bildschirme lesen muss
Faden

Am Donnerstag, den 7. Januar 2016, hat Andy Edwards < [email protected]
<_e i = "16">

Ja, ich weiß ... alles was ich sage ist, dass dieser Thread einen Mangel an illustriert hat
Konsens (zumindest unter den Menschen hier) über die beste Art, Seite zu leisten
Auswirkungen.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rackt/redux/issues/291#issuecomment -169751432.

Dieser und ähnliche Threads existieren, weil 1% der Redux-Benutzer nach leistungsfähigeren / reineren / deklarativen Lösungen für Nebenwirkungen suchen. Bitte nehmen Sie nicht den Eindruck, dass sich niemand darauf einigen kann. Die stille Mehrheit verwendet Gedanken und Versprechen und ist mit diesem Ansatz größtenteils zufrieden. Aber wie jede Technologie hat sie Nachteile und Kompromisse. Wenn Sie über eine komplexe asynchrone Logik verfügen, möchten Sie möglicherweise Redux löschen und stattdessen Rx untersuchen. Das Redux-Muster ist in Rx leicht ausdrucksstark.

OK danke

Am Donnerstag, dem 7. Januar 2016, schrieb Dan Abramov [email protected] :

Dieser und ähnliche Threads existieren, weil 1% der Redux-Benutzer gerne suchen
leistungsstärkere / reine / deklarative Lösungen für Nebenwirkungen. Bitte nicht
Nehmen Sie den Eindruck, dass sich niemand darauf einigen kann. Die stille Mehrheit verwendet
denkt und verspricht und ist meistens mit diesem Ansatz zufrieden. Aber wie jeder andere
Technologie hat es Nachteile und Kompromisse. Wenn Sie eine komplexe asynchrone Logik haben
Vielleicht möchten Sie Redux fallen lassen und stattdessen Rx erkunden. Redux-Muster ist
leicht ausdrucksstark in Rx.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rackt/redux/issues/291#issuecomment -169761410.

@gaearon ja, du hast recht. Und ich schätze, dass Redux flexibel genug ist, um all unsere unterschiedlichen Ansätze zu berücksichtigen!

@ halt-hammerzeit Werfen Sie einen Blick auf meine Antwort hier, wo ich erkläre, warum Redux-Saga besser sein kann als Redux-Thunk: http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for- Async-Flow-In-Redux / 34599594

@gaearon Ihre Antwort auf stackoverflow hat übrigens den gleichen Fehler wie die Dokumentation - sie deckt nur den einfachsten Fall ab
http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
Reale Anwendungen gehen schnell über das Abrufen von Daten über AJAX hinaus: Sie sollten auch Fehler behandeln und einige Aktionen ausführen, abhängig von der Ausgabe des Aktionsaufrufs.
Ihr Rat ist, nur dispatch ein anderes Ereignis zu veranstalten, und das war's.
Was ist, wenn mein Code einem Benutzer nach Abschluss der Aktion alert() möchte?
Dort werden diese thunk nicht helfen, aber Versprechen werden es tun.
Ich schreibe derzeit meinen Code mit einfachen Versprechungen und es funktioniert so.

Ich habe den nebenstehenden Kommentar zu redux-saga gelesen.
Ich habe nicht verstanden, was es tut, lol.
Ich mag das monads -Ding und so nicht so sehr, und ich weiß immer noch nicht, was ein thunk ist, und ich mag dieses seltsame Wort zunächst nicht.

Ok, dann verwende ich weiterhin Promises in React-Komponenten.

Wir empfehlen, in den Dokumenten Dokumente von Thunks zurückzugeben.

@gaearon Ja, das ist es, worüber ich spreche: on_click() { dispatch(load_stuff()).then(show_modal('done')) }
Das wird reichen.

Ich denke, dir fehlt noch etwas.
Bitte lesen Sie README von Redux-Thunk.
Im Abschnitt "Komposition" wird gezeigt, wie Thunk-Action-Ersteller verkettet werden.

@gaearon Ja, genau das mache ich:

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});

Genau das habe ich oben geschrieben:

on_click()
{
    dispatch(load_stuff())
        .then(() => show_modal('done'))
        .catch(() => show_error('not done'))
}

Dieser README-Abschnitt ist gut geschrieben, danke

Nein, das war nicht mein Punkt. Schauen Sie sich makeSandwichesForEverybody . Es ist ein Thunk-Action-Schöpfer, der andere Thunk-Action-Schöpfer anruft. Aus diesem Grund müssen Sie nicht alles in Komponenten einfügen. Weitere Informationen hierzu finden Sie im Beispiel async in diesem Repo.

@gaearon Aber ich denke, es wäre nicht angebracht, beispielsweise meinen ausgefallenen Animationscode in den Action Creator zu integrieren, oder?
Betrachten Sie dieses Beispiel:

button_on_click()
{
    this.props.dispatch(load_stuff())
        .then(() =>
        {
                const modal = ReactDOM.getDOMNode(this.refs.modal)
                jQuery(modal).fancy_animation(1200 /* ms */)
                setTimeout(() => jQuery.animate(modal, { background: 'red' }), 1500 /* ms */)
        })
        .catch(() =>
        {
                alert('Failed to bypass the root mainframe protocol to override the function call on the hyperthread's secondary firewall')
        })
}

Wie würden Sie es richtig umschreiben?

@ halt-hammerzeit Sie können den actionCreator dazu bringen, die Versprechen zurückzugeben, damit die Komponente einen Spinner oder was auch immer anzeigen kann, indem Sie den lokalen Komponentenstatus verwenden (schwer zu vermeiden, wenn Sie jquery verwenden).

Andernfalls können Sie komplexe Timer verwalten, um Animationen mit Redux-Saga zu steuern.

Schauen Sie sich diesen Blog-Beitrag an: http://jaysoo.ca/2016/01/03/managing-processes-in-redux-using-sagas/

Oh, in diesem Fall ist es in Ordnung, es in der Komponente zu behalten.
(Hinweis: Im Allgemeinen ist es in React am besten, ein deklaratives Modell für alles zu verwenden, anstatt mit jQuery unbedingt Modalitäten anzuzeigen. Aber keine große Sache.)

@slorber Ja, ich

@gaearon Ok, ich hab dich. In der idealen Maschinenwelt wäre es natürlich möglich, innerhalb des deklarativen Programmiermodells zu bleiben, aber die Realität hat ihre eigenen Anforderungen, beispielsweise irrational aus Sicht einer Maschine. Menschen sind irrationale Wesen, die nicht nur mit reinen Nullen und Einsen arbeiten, sondern auch die Schönheit und Reinheit des Codes beeinträchtigen müssen, um irrationale Dinge tun zu können.

Ich bin mit der Unterstützung in diesem Thread zufrieden. Meine Zweifel scheinen jetzt gelöst zu sein.

@gaearon sehr tolle Erklärung! : trophy: Danke: +1:

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen