Puis-je envoyer une action dans le réducteur?

196

est-il possible d'envoyer une action dans un réducteur lui-même? J'ai une barre de progression et un élément audio. L'objectif est de mettre à jour la barre de progression lorsque l'heure est mise à jour dans l'élément audio. Mais je ne sais pas où placer le gestionnaire d'événements ontimeupdate, ni comment envoyer une action dans le rappel de ontimeupdate, pour mettre à jour la barre de progression. Voici mon 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;
klanm
la source
Qu'est-ce que c'est AudioElement? Il semble que cela ne devrait pas être quelque chose en état.
ebuat3989
il s'agit d'une classe simple ES6 (sans réaction), contenant un objet audio. Si ce n'est pas le cas, comment contrôler la lecture / l'arrêt, le saut, etc.?
klanm
2
Vous voudrez peut-être vous pencher sur la saga redux
Kyeotic

Réponses:

150

L'envoi d'une action dans un réducteur est un anti-motif . Votre réducteur doit être sans effets secondaires, il suffit de digérer la charge utile de l'action et de renvoyer un nouvel objet d'état. L'ajout d'écouteurs et la répartition des actions dans le réducteur peuvent entraîner des actions en chaîne et d'autres effets secondaires.

Des sons comme votre AudioElementclasse initialisée et l'écouteur d'événements appartiennent à un composant plutôt qu'à l'état. Dans l'écouteur d'événements, vous pouvez envoyer une action, qui sera mise progressà jour dans l'état.

Vous pouvez soit initialiser l' AudioElementobjet classe dans un nouveau composant React, soit simplement convertir cette classe en composant 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);

Notez que le updateProgressActionest automatiquement entouré de dispatchsorte que vous n'avez pas besoin d'appeler directement le dispatch.

ebuat3989
la source
merci beaucoup pour la clarification! Mais je ne sais toujours pas comment accéder au répartiteur. J'ai toujours utilisé la méthode connect de react-redux. mais je ne sais pas comment l'appeler dans la méthode updateProgress. Ou existe-t-il un autre moyen d'obtenir le répartiteur? peut-être avec des accessoires? merci
klanm
Aucun problème. Vous pouvez transmettre l'action au MyAudioPlayercomposant à partir du conteneur parent qui est connectédité avec react-redux. Découvrez comment le faire mapDispatchToPropsici: github.com/reactjs/react-redux/blob/master/docs/…
ebuat3989
6
Où le symbole est-il updateProgressActiondéfini dans votre exemple?
Charles Prakash Dasari
2
Si vous n'êtes pas censé envoyer une action dans un réducteur, alors redux-thunk enfreint-il les règles de redux?
Eric Wiener
2
@EricWiener, je crois, redux-thunkenvoie une action à partir d'une autre action, pas le réducteur. stackoverflow.com/questions/35411423/…
sallf
160

Démarrer une autre expédition avant la fin de votre réducteur est un anti-modèle , car l'état que vous avez reçu au début de votre réducteur ne sera plus l'état actuel de l'application lorsque votre réducteur aura terminé. Mais planifier un autre envoi depuis un réducteur n'est PAS un anti-modèle . En fait, c'est ce que fait le langage Elm, et comme vous le savez, Redux est une tentative d'amener l'architecture Elm en JavaScript.

Voici un middleware qui ajoutera la propriété asyncDispatchà toutes vos actions. Lorsque votre réducteur a terminé et renvoyé le nouvel état de l'application, il asyncDispatchse déclenchera store.dispatchavec l'action que vous lui donnerez.

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

Maintenant, votre réducteur peut le faire:

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
la source
7
Marcelo, votre article de blog ici fait un excellent travail décrivant les circonstances de votre approche, donc je le relie ici: lazamar.github.io/dispatching-from-inside-of-reducers
Dejay Clayton
3
C'était exactement ce dont j'avais besoin, à l'exception des interruptions telles que celles du middleware dispatchqui devraient renvoyer l'action. J'ai changé les dernières lignes en: const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res;et cela a très bien fonctionné.
zanerock
1
Si vous n'êtes pas censé envoyer une action dans un réducteur, alors redux-thunk enfreint-il les règles de redux?
Eric Wiener
Comment cela fonctionne-t-il lorsque vous essayez de gérer les réponses Websocket? Ceci est ma valeur par défaut d'exportation de réducteur (état, action) => {const m2 = [... state.messages, action.payload] return Object.assign ({}, state, {messages: m2,})} et I STILL obtenir cette erreur "une mutation d'état a été détectée entre les dépêches"
quantumpotato
12

Vous pourriez essayer d'utiliser une bibliothèque comme redux-saga . Il permet un moyen très propre de séquencer les fonctions asynchrones, de déclencher des actions, d'utiliser des retards et plus encore. C'est très puissant!

chandlervdw
la source
1
pouvez-vous spécifier comment réaliser la «planification d'une autre expédition dans le réducteur» avec redux-saga?
XPD
1
@XPD pouvez-vous expliquer un peu plus ce que vous voulez accomplir? Si vous essayez d'utiliser une action de réduction pour envoyer une autre action, vous ne pourrez pas vous passer de quelque chose qui ressemble à redux-saga.
chandlervdw
1
Par exemple, supposons que vous ayez un magasin d'articles où vous avez chargé une partie des articles. Les articles sont chargés paresseusement. Supposons qu'un article ait un fournisseur. Les fournisseurs ont également chargé paresseusement. Donc, dans ce cas, il pourrait y avoir un article que son fournisseur n'a pas été chargé. Dans un réducteur d'élément, si nous devons obtenir des informations sur un élément qui n'a pas encore été chargé, nous devons à nouveau charger les données du serveur via un réducteur. Dans ce cas, comment redux-saga aide-t-il à l'intérieur d'un réducteur?
XPD
1
Ok, supposons que vous vouliez déclencher cette demande d' supplierinformations lorsque l'utilisateur tente de visiter la itempage de détails. Par exemple, vous componentDidMount()déclencheriez une fonction qui envoie une action FETCH_SUPPLIER. Dans le réducteur, vous pouvez cocher quelque chose comme un loading: truepour montrer un spinner pendant la demande. redux-sagaécouterait cette action et, en réponse, lancerait la demande réelle. En utilisant les fonctions du générateur, il peut alors attendre la réponse et la vider FETCH_SUPPLIER_SUCCEEDED. Le réducteur met ensuite à jour le magasin avec les informations du fournisseur.
chandlervdw
6

redux-loop s'inspire d'Elm et fournit ce modèle.

Quang Van
la source