¿Puedo enviar una acción en reductor?


196

¿Es posible despachar 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 el tiempo se actualiza en el elemento de audio. Pero no sé dónde colocar el controlador de eventos ontimeupdate, ni 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;

¿Qué es AudioElement? Parece que eso no debería ser algo en estado.
ebuat3989

es una clase simple ES6 (sin reacción), que contiene un objeto de audio Si no estuviera en el estado, ¿cómo controlaría play / stop, skipping, etc.?
klanm

2
Es posible que desee ver en la saga redux
Kyeotic

Respuestas:


150

El envío de una acción dentro de un reductor es un antipatrón . Su reductor no debe tener efectos secundarios, simplemente digiere la carga útil de la acción y devuelve un nuevo objeto de estado. Agregar oyentes y enviar acciones dentro del reductor puede conducir a acciones encadenadas y otros efectos secundarios.

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

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

Tenga en cuenta que updateProgressActionse envuelve automáticamente, por dispatchlo que no necesita llamar al despacho directamente.


¡Muchas gracias por la aclaración! Pero todavía no sé cómo acceder al despachador. Siempre usé el método de conexión de react-redux. pero no sé cómo llamarlo en el método updateProgress. ¿O hay otra forma de obtener el despachador? tal vez con accesorios? gracias
klanm

No hay problema. Puede pasar la acción al MyAudioPlayercomponente desde el contenedor principal con el que se connectedita react-redux. Mira cómo hacerlo mapDispatchToPropsaquí: github.com/reactjs/react-redux/blob/master/docs/…
ebuat3989

66
¿Dónde se updateProgressActiondefine el símbolo en tu ejemplo?
Charles Prakash Dasari

2
Si se supone que no debe enviar una acción dentro de un reductor, ¿está redux-thunk rompiendo las reglas de redux?
Eric Wiener

2
@EricWiener Creo que redux-thunkestá enviando una acción de otra acción, no del reductor. stackoverflow.com/questions/35411423/…
sallf

160

Iniciar otro despacho antes de que termine su reductor es un antipatrón , porque el estado que recibió al comienzo de su reductor ya no será el estado actual de la aplicación 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 saben, 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 finalizado y devuelto el 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 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;
};

Ahora su 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 });;
  }
}

77
Marcelo, tu publicación de blog aquí hace un gran trabajo describiendo las circunstancias de tu enfoque, así que estoy enlazando aquí: lazamar.github.io/dispatching-from-inside-of-reducers
Dejay Clayton

3
Esto era exactamente lo que necesitaba, excepto las interrupciones del middleware tal dispatchcual, que deberían devolver la acción. Cambié las últimas líneas a: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;y funcionó muy bien.
zanerock

1
Si se supone que no debe enviar una acción dentro de un reductor, ¿está redux-thunk rompiendo las reglas de redux?
Eric Wiener

¿Cómo funciona esto cuando intentas manejar las respuestas de websocket? Este es el valor predeterminado de exportación de mi reductor (estado, acción) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, state, {messages: m2,})} y TODAVÍA obtener "se detectó la mutación en el estado entre los despachos" este error
quantumpotato

12

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


1
¿puede especificar cómo lograr 'programar otro despacho dentro del reductor' con redux-saga?
XPD

1
@XPD, ¿puedes explicar un poco más lo que quieres lograr? Si está intentando usar una acción reductora para enviar otra acción, no podrá hacerlo sin algo parecido a la saga redux.
chandlervdw

1
Por ejemplo, suponga que tiene una tienda de artículos donde ha cargado una parte de los artículos. Los artículos se cargan perezosamente. Suponga que un artículo tiene un proveedor. Los proveedores también cargaron perezosamente. Entonces, en este caso, podría haber un artículo que su proveedor no haya cargado. En un reductor de elementos, si necesitamos obtener información sobre un elemento que aún no se ha cargado, tenemos que volver a cargar datos del servidor a través de un reductor. En ese caso, ¿cómo ayuda redux-saga dentro de un reductor?
XPD

1
Bien, supongamos que desea activar esta solicitud de supplierinformación cuando el usuario intenta visitar la itempágina de detalles. Tu componentDidMount()dispararías una función que despacha una acción, por ejemplo FETCH_SUPPLIER. Dentro del reductor, puede marcar algo como a loading: truepara mostrar una rueda giratoria mientras se realiza la solicitud. redux-sagaescucharía esa acción y, en respuesta, dispararía la solicitud real. Utilizando las funciones del generador, puede esperar la respuesta y volcarla FETCH_SUPPLIER_SUCCEEDED. El reductor luego actualiza la tienda con información del proveedor.
chandlervdw

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.