Posso inviare un'azione nel riduttore?


196

è possibile inviare un'azione in un riduttore stesso? Ho una barra di avanzamento e un elemento audio. L'obiettivo è aggiornare la barra di avanzamento quando il tempo viene aggiornato nell'elemento audio. Ma non so dove posizionare il gestore eventi ontimeupdate o come inviare un'azione nel callback di ontimeupdate per aggiornare la barra di avanzamento. Ecco il mio codice:

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

Che cosa è AudioElement? Sembra che non dovrebbe essere qualcosa nello stato.
ebuat3989,

è una classe semplice ES6 (nessuna reazione), contenente un oggetto audio. Se non fosse nello stato, come controllerei play / stop, skipping ecc.?
klanm,

2
Potresti voler esaminare la saga di
Redux

Risposte:


150

L'invio di un'azione all'interno di un riduttore è un anti-schema . Il riduttore dovrebbe essere privo di effetti collaterali, semplicemente digerendo il payload dell'azione e restituendo un nuovo oggetto stato. L'aggiunta di ascoltatori e l'invio di azioni all'interno del riduttore possono comportare azioni concatenate e altri effetti collaterali.

Sembra che la AudioElementclasse inizializzata e il listener di eventi appartengano a un componente anziché allo stato. All'interno del listener di eventi è possibile inviare un'azione, che verrà aggiornataprogress nello stato.

Puoi inizializzare l' AudioElementoggetto classe in un nuovo componente React o semplicemente convertire quella classe in un componente React.

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

Si noti che updateProgressActionviene automaticamente spostato con, dispatchquindi non è necessario chiamare direttamente la spedizione.


grazie mille per il chiarimento! Ma non so ancora come accedere al dispatcher. Ho sempre usato il metodo di connessione di reagire-redux. ma non so come chiamarlo nel metodo updateProgress. O c'è un altro modo per ottenere il dispatcher. forse con oggetti di scena? grazie
klanm,

Nessun problema. È possibile passare l'azione al MyAudioPlayercomponente dal contenitore principale con cui è stato connecteditato react-redux. Scopri come farlo con mapDispatchToPropsqui: github.com/reactjs/react-redux/blob/master/docs/…
ebuat3989

6
Dove viene updateProgressActiondefinito il simbolo nel tuo esempio?
Charles Prakash Dasari,

2
Se non dovresti inviare un'azione all'interno di un riduttore, allora il redux-thunk infrange le regole del redux?
Eric Wiener,

2
@EricWiener Credo che redux-thunkstia inviando un'azione da un'altra azione, non dal riduttore. stackoverflow.com/questions/35411423/...
sallf

160

L'avvio di un'altra spedizione prima che il riduttore sia terminato è un modello anti-modello , poiché lo stato ricevuto all'inizio del riduttore non sarà più lo stato corrente dell'applicazione al termine del riduttore. Ma programmare un'altra spedizione dall'interno di un riduttore NON è un modello . In effetti, questo è ciò che fa il linguaggio Elm, e come sapete Redux è un tentativo di portare l'architettura Elm in JavaScript.

Ecco un middleware che aggiungerà la proprietà asyncDispatcha tutte le tue azioni. Quando il riduttore ha terminato e restituito il nuovo stato dell'applicazione, asyncDispatchsi innescherà store.dispatchcon qualsiasi azione tu gli dia.

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

Ora il tuo riduttore può fare questo:

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, il tuo post sul blog fa un ottimo lavoro descrivendo le circostanze del tuo approccio, quindi sto collegando ad esso qui: lazamar.github.io/dispatching-from-inside-of-reducers
Dejay Clayton

3
Questo era esattamente ciò di cui avevo bisogno, tranne il middleware così com'è dispatchche dovrebbe restituire l'azione. Ho cambiato le ultime righe in: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;e ha funzionato alla grande.
zanerock,

1
Se non dovresti inviare un'azione all'interno di un riduttore, allora il redux-thunk infrange le regole del redux?
Eric Wiener,

Come funziona quando si tenta di gestire le risposte al websocket? Questo è il mio riduttore export default (state, action) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, state, {message: m2,})} e I STILL ottiene questo errore "È stata rilevata una mutazione di stato tra i dispacci"
quantumpotato,

12

Potresti provare a usare una libreria come redux-saga . Consente un modo molto pulito per sequenziare le funzioni asincrone, eseguire azioni, utilizzare ritardi e altro. È molto potente!


1
puoi specificare come realizzare 'programmare un altro dispaccio all'interno del riduttore' con redux-saga?
XPD

1
@XPD puoi spiegare un po 'di più cosa vuoi realizzare? Se stai cercando di usare un'azione riduttore per inviare un'altra azione, non potrai farlo senza qualcosa di simile alla redux-saga.
chandlervdw,

1
Ad esempio, supponiamo di avere un negozio di articoli in cui è stata caricata una parte degli articoli. Gli articoli vengono caricati pigramente. Supponiamo che un articolo abbia un fornitore. Anche i fornitori hanno caricato pigramente. Quindi in questo caso potrebbe esserci un articolo che il suo fornitore non è stato caricato. In un riduttore di articoli, se è necessario ottenere informazioni su un articolo che non è stato ancora caricato, è necessario caricare nuovamente i dati dal server tramite un riduttore. In tal caso, in che modo la redux-saga aiuta all'interno di un riduttore?
XPD

1
Ok, supponiamo che tu voglia attivare questa richiesta di supplierinformazioni quando l'utente tenta di visitare la itempagina dei dettagli. Il tuo componentDidMount()sarebbe sparare una funzione che invia un'azione, per esempio FETCH_SUPPLIER. All'interno del riduttore, è possibile selezionare qualcosa come un loading: trueper mostrare uno spinner mentre viene effettuata la richiesta. redux-sagaascolterebbe quell'azione e, in risposta, licenzierebbe la richiesta effettiva. Utilizzando le funzioni del generatore, può quindi attendere la risposta e scaricarla FETCH_SUPPLIER_SUCCEEDED. Il riduttore quindi aggiorna il negozio con le informazioni sul fornitore.
chandlervdw,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.