¿Puedo enviar una acción en reductor?

Resuelto klanm asked hace 8 años • 7 respuestas

¿Es posible enviar una acción en un reductor? Tengo una barra de progreso y un elemento de audio. El objetivo es actualizar la barra de progreso cuando se actualiza la hora en el elemento de audio. Pero no sé dónde colocar el controlador de eventos de ontimeupdate o cómo enviar una acción en la devolución de llamada de ontimeupdate para actualizar la barra de progreso. Aquí está mi código:

//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;
klanm avatar Apr 20 '16 05:04 klanm
Aceptado

Iniciar otro envío antes de que finalice su reductor es un antipatrón , porque el estado que recibió al comienzo de su reductor ya no será el estado de la aplicación actual cuando finalice su reductor. Pero programar otro envío desde un reductor NO es un antipatrón . De hecho, eso es lo que hace el lenguaje Elm y, como sabes, Redux es un intento de llevar la arquitectura Elm a JavaScript.

Aquí hay un middleware que agregará la propiedad asyncDispatcha todas sus acciones. Cuando su reductor haya terminado y haya regresado al nuevo estado de la aplicación, asyncDispatchse activará store.dispatchcon cualquier acción que le dé.

// This middleware will just add the property "async dispatch" to all actions
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;
};

Ahora tu reductor puede hacer esto:

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 });;
  }
}
Marcelo Lazaroni avatar Dec 21 '2016 10:12 Marcelo Lazaroni

Enviar una acción dentro de un reductor es un antipatrón . Su reductor no debe tener efectos secundarios, simplemente digerir la carga útil de la acción y devolver un nuevo objeto de estado. Agregar oyentes y enviar acciones dentro del reductor puede generar acciones encadenadas y otros efectos secundarios.

Parece que su AudioElementclase inicializada y el detector de eventos pertenecen a un componente en lugar de a un estado. Dentro del detector de eventos puedes enviar una acción, que se actualizará progressen el estado.

Puede inicializar el AudioElementobjeto de clase en un nuevo componente de React o simplemente convertir esa clase en un componente de 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);

Tenga en cuenta que el updateProgressActionse ajusta automáticamente, dispatchpor lo que no es necesario llamar al despacho directamente.

bogeylicious avatar Apr 19 '2016 23:04 bogeylicious

Podrías intentar usar una biblioteca como redux-saga . Permite una forma muy limpia de secuenciar funciones asíncronas, realizar acciones, utilizar retrasos y más. ¡Es muy poderoso!

chandlervdw avatar Sep 08 '2016 14:09 chandlervdw

redux-loop sigue el ejemplo de Elm y proporciona este patrón.

Quang Van avatar Apr 22 '2018 05:04 Quang Van