Kann ich eine Aktion im Reduzierer auslösen?


196

Ist es möglich, eine Aktion in einem Reduzierer selbst auszulösen? Ich habe einen Fortschrittsbalken und ein Audioelement. Ziel ist es, den Fortschrittsbalken zu aktualisieren, wenn die Zeit im Audioelement aktualisiert wird. Ich weiß jedoch nicht, wo der Ereignishandler ontimeupdate platziert werden soll oder wie eine Aktion im Rückruf von ontimeupdate ausgelöst werden soll, um den Fortschrittsbalken zu aktualisieren. Hier ist mein Code:

//reducer

const initialState = {
    audioElement: new AudioElement('test.mp3'),
    progress: 0.0
}

initialState.audioElement.audio.ontimeupdate = () => {
    console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
    //how to dispatch 'SET_PROGRESS_VALUE' now?
};


const audio = (state=initialState, action) => {
    switch(action.type){
        case 'SET_PROGRESS_VALUE':
            return Object.assign({}, state, {progress: action.progress});
        default: return state;
    }

}

export default audio;

Was ist AudioElement? Es scheint, dass das nicht in einem Zustand sein sollte.
ebuat3989

Es ist eine einfache ES6-Klasse (keine Reaktion), die ein Audioobjekt enthält. Wenn es nicht in dem Zustand wäre, wie würde ich das Spielen / Stoppen, Überspringen usw. steuern?
Klanm

2
Vielleicht möchten Sie in Redux Saga
Kyeotic

Antworten:


150

Das Auslösen einer Aktion innerhalb eines Reduzierers ist ein Anti-Muster . Ihr Reduzierer sollte keine Nebenwirkungen haben, einfach die Nutzdaten der Aktion verdauen und ein neues Statusobjekt zurückgeben. Das Hinzufügen von Listenern und das Auslösen von Aktionen innerhalb des Reduzierers kann zu verketteten Aktionen und anderen Nebenwirkungen führen.

Klingt so, als ob Ihre initialisierte AudioElementKlasse und der Ereignis-Listener eher zu einer Komponente als zu einem Status gehören. Innerhalb des Ereignis-Listeners können Sie eine Aktion auslösen, die progressim Status aktualisiert wird .

Sie können das AudioElementKlassenobjekt entweder in einer neuen React-Komponente initialisieren oder diese Klasse einfach in eine React-Komponente konvertieren.

class MyAudioPlayer extends React.Component {
  constructor(props) {
    super(props);

    this.player = new AudioElement('test.mp3');

    this.player.audio.ontimeupdate = this.updateProgress;
  }

  updateProgress () {
    // Dispatch action to reducer with updated progress.
    // You might want to actually send the current time and do the
    // calculation from within the reducer.
    this.props.updateProgressAction();
  }

  render () {
    // Render the audio player controls, progress bar, whatever else
    return <p>Progress: {this.props.progress}</p>;
  }
}

class MyContainer extends React.Component {
   render() {
     return <MyAudioPlayer updateProgress={this.props.updateProgress} />
   }
}

function mapStateToProps (state) { return {}; }

return connect(mapStateToProps, {
  updateProgressAction
})(MyContainer);

Beachten Sie, dass das updateProgressActionautomatisch verpackt wird, dispatchsodass Sie den Versand nicht direkt anrufen müssen.


Vielen Dank für die Klarstellung! Aber ich weiß immer noch nicht, wie ich auf den Dispatcher zugreifen soll. Ich habe immer die Connect-Methode von React-Redux verwendet. Ich weiß jedoch nicht, wie ich es in der updateProgress-Methode aufrufen soll. Oder gibt es einen anderen Weg, um den Dispatcher zu bekommen. vielleicht mit Requisiten? danke
klanm

Kein Problem. Sie können die Aktion MyAudioPlayeraus dem übergeordneten Container, connectmit dem sie bearbeitet wurde, an die Komponente übergeben react-redux. mapDispatchToProps
Lesen Sie

6
Wo ist das Symbol updateProgressActionin Ihrem Beispiel definiert?
Charles Prakash Dasari

2
Wenn Sie keine Aktion innerhalb eines Reduzierers auslösen sollen, verstößt dann Redux-Thunk gegen die Regeln von Redux?
Eric Wiener

2
@EricWiener Ich glaube, redux-thunkeine Aktion wird von einer anderen Aktion ausgelöst , nicht vom Reduzierer. stackoverflow.com/questions/35411423/…
sallf

159

Das Starten eines weiteren Versands, bevor Ihr Reduzierer fertig ist, ist ein Anti-Muster , da der Status, den Sie zu Beginn Ihres Reduzierers erhalten haben, nicht mehr der aktuelle Anwendungsstatus ist, wenn Ihr Reduzierer fertig ist . Das Planen eines weiteren Versands innerhalb eines Reduzierers ist jedoch KEIN Anti-Pattern . Genau das macht die Elm-Sprache, und wie Sie wissen, ist Redux ein Versuch, die Elm-Architektur auf JavaScript zu bringen.

Hier ist eine Middleware, die die Eigenschaft asyncDispatchallen Ihren Aktionen hinzufügt . Wenn Ihr Reduzierer fertig ist und den neuen Anwendungsstatus zurückgegeben hat, asyncDispatchwird er store.dispatchmit jeder Aktion ausgelöst, die Sie ihm geben.

// This middleware will just add the property "async dispatch"
// to actions with the "async" propperty set to true
const asyncDispatchMiddleware = store => next => action => {
  let syncActivityFinished = false;
  let actionQueue = [];

  function flushQueue() {
    actionQueue.forEach(a => store.dispatch(a)); // flush queue
    actionQueue = [];
  }

  function asyncDispatch(asyncAction) {
    actionQueue = actionQueue.concat([asyncAction]);

    if (syncActivityFinished) {
      flushQueue();
    }
  }

  const actionWithAsyncDispatch =
    Object.assign({}, action, { asyncDispatch });

  const res = next(actionWithAsyncDispatch);

  syncActivityFinished = true;
  flushQueue();

  return res;
};

Jetzt kann Ihr Reduzierer dies tun:

function reducer(state, action) {
  switch (action.type) {
    case "fetch-start":
      fetch('wwww.example.com')
        .then(r => r.json())
        .then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
      return state;

    case "fetch-response":
      return Object.assign({}, state, { whatever: action.value });;
  }
}

7
Marcelo, Ihr Blog-Beitrag hier macht einen großartigen Job und beschreibt die Umstände Ihres Ansatzes. Deshalb
verlinke

3
Dies war genau das, was ich brauchte, mit Ausnahme der Middleware-Ist-Pausen, dispatchdie die Aktion zurückgeben sollten. Ich habe die letzten Zeilen geändert in: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;und es hat super funktioniert.
Zanerock

1
Wenn Sie keine Aktion innerhalb eines Reduzierers auslösen sollen, verstößt dann Redux-Thunk gegen die Regeln von Redux?
Eric Wiener

Wie funktioniert das, wenn Sie versuchen, Websocket-Antworten zu verarbeiten? Dies ist mein Reduzierer-Exportstandard (Status, Aktion) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, state, {messages: m2,})} und I STILL
Erhalten

12

Sie könnten versuchen, eine Bibliothek wie Redux-Saga zu verwenden . Es ermöglicht eine sehr saubere Möglichkeit, asynchrone Funktionen zu sequenzieren, Aktionen auszulösen, Verzögerungen zu verwenden und vieles mehr. Es ist sehr mächtig!


1
Können Sie angeben, wie mit Redux-Saga die Planung eines weiteren Versands innerhalb des Reduzierers erreicht werden soll?
XPD

1
@XPD Kannst du etwas mehr erklären, was du erreichen willst? Wenn Sie versuchen, eine Reduzierungsaktion zu verwenden, um eine andere Aktion auszulösen, können Sie nicht auf etwas verzichten, das der Redux-Saga ähnelt.
Chandlervdw

1
Angenommen, Sie haben einen Artikelspeicher, in den Sie einen Teil der Artikel geladen haben. Artikel werden träge geladen. Angenommen, ein Artikel hat einen Lieferanten. Lieferanten luden auch träge. In diesem Fall gibt es möglicherweise einen Artikel, dessen Lieferant nicht geladen wurde. Wenn wir in einem Artikelreduzierer Informationen über einen Artikel erhalten möchten, der noch nicht geladen wurde, müssen wir die Daten erneut über einen Reduzierer vom Server laden. Wie hilft in diesem Fall die Redux-Saga in einem Reduzierer?
XPD

1
Angenommen, Sie wollten diese supplierInformationsanfrage auslösen, wenn der Benutzer versucht, die itemDetailseite zu besuchen . Sie componentDidMount()würden beispielsweise eine Funktion auslösen, die eine Aktion auslöst FETCH_SUPPLIER. Innerhalb des Reduzierers können Sie so etwas wie ein ankreuzen, loading: trueum einen Spinner anzuzeigen, während die Anfrage gestellt wird. redux-sagawürde auf diese Aktion warten und als Antwort die eigentliche Anfrage abfeuern. Mithilfe von Generatorfunktionen kann es dann auf die Antwort warten und sie ausgeben FETCH_SUPPLIER_SUCCEEDED. Der Reduzierer aktualisiert dann das Geschäft mit Lieferanteninformationen.
Chandlervdw

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