236 votes

Puis-je dispatcher une action dans le reducer ?

Est-il possible de dispatcher une action dans un reducer 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 l'eventhandler ontimeupdate, ni comment envoyer une action dans le callback 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;

0 votes

Qu'est-ce que AudioElement ? Il me semble que ça ne devrait pas être quelque chose d'officiel.

0 votes

Il s'agit d'une classe ES6 simple (sans réaction), contenant un objet audio. Si elle n'était pas dans l'état, comment pourrais-je contrôler la lecture/arrêt, le saut, etc. ?

2 votes

Vous pourriez vouloir regarder dans redux saga

196voto

Marcelo Lazaroni Points 3248

Lancer un autre envoi avant que votre réducteur soit terminé 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 se terminera. Mais La programmation d'un autre envoi à partir d'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'apporter l'architecture Elm à JavaScript.

Voici un middleware qui va ajouter la propriété asyncDispatch à toutes vos actions. Lorsque votre réducteur a terminé et renvoyé le nouvel état de l'application, asyncDispatch déclenchera store.dispatch avec n'importe quelle action que vous lui donnez.

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

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

10 votes

Marcelo, votre article de blog décrit très bien les circonstances de votre approche, c'est pourquoi je le mets en lien ici : lazamar.github.io/dispatching-from-inside-of-reducers

4 votes

C'est exactement ce dont j'avais besoin, sauf que le middleware tel quel se casse la figure. dispatch qui devrait renvoyer l'action. J'ai changé les dernières lignes en : const res = next(actionWithAsyncDispatch); syncActivityFinished = true; flushQueue(); return res; et ça a bien marché.

3 votes

Si vous n'êtes pas censé envoyer une action dans un réducteur, alors redux-thunk enfreint-il les règles de redux ?

181voto

mtaube Points 543

L'envoi d'une action dans un réducteur est un anti-modèle. . Votre réducteur ne doit pas avoir d'effets secondaires, il doit simplement digérer la charge utile de l'action et renvoyer un nouvel objet d'état. L'ajout de récepteurs et la répartition des actions dans le réducteur peuvent entraîner des actions en chaîne et d'autres effets secondaires.

On dirait que votre initialisation AudioElement et l'écouteur d'événement appartiennent à un composant plutôt qu'à un état. Dans l'écouteur d'événements, vous pouvez envoyer une action, qui mettra à jour progress dans l'État.

Vous pouvez soit initialiser le AudioElement dans un nouveau composant React ou simplement convertir cette classe en un 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 updateProgressAction est automatiquement entouré de dispatch de sorte que vous n'avez pas besoin d'appeler directement la répartition.

0 votes

Merci beaucoup pour cette 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 bien existe-t-il un autre moyen d'obtenir le distributeur, peut-être avec des props ?

0 votes

Pas de problème. Vous pouvez passer l'action à la MyAudioPlayer du conteneur parent qui est connect ed avec react-redux . Découvrez comment faire avec mapDispatchToProps ici : github.com/reactjs/react-redux/blob/master/docs/

0 votes

Ahhhh bien, maintenant je comprends les concepts ! Ça marche ! Merci beaucoup ! C'est toujours de la magie mystique pour moi :)

18voto

chandlervdw Points 362

Vous pouvez essayer d'utiliser une bibliothèque comme redux-saga . Il permet de très Une façon propre de séquencer des fonctions asynchrones, de déclencher des actions, d'utiliser des délais et plus encore. C'est très puissant !

4 votes

Pouvez-vous spécifier comment réaliser 'la planification d'une autre répartition à l'intérieur du réducteur' avec redux-saga ?

1 votes

@XPD pouvez-vous expliquer un peu plus ce que vous voulez accomplir ? Si vous essayez d'utiliser une action de réducteur pour distribuer une autre action, vous ne pourrez pas le faire sans quelque chose qui ressemble à redux-saga.

2 votes

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 sont également chargés paresseusement. Donc, dans ce cas, il pourrait y avoir un article dont le fournisseur n'a pas été chargé. Dans un réducteur d'éléments, si nous avons besoin d'obtenir des informations sur un élément qui n'a pas encore été chargé, nous devons recharger les données du serveur à travers un réducteur. Dans ce cas, comment redux-saga peut-il aider dans un réducteur ?

7voto

Quang Van Points 1795

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

0voto

JillAndMe Points 134

Le dispatching et l'action à l'intérieur du réducteur semble être un bug.

J'ai fait un simple exemple de compteur en utilisant useReducer si "INCREASE" est distribué, alors "SUB" l'est aussi.

Dans l'exemple que je prévoyais, "INCREASE" est distribué, puis "SUB" l'est aussi, ainsi que "set". cnt à -1 et ensuite poursuivre l'action "INCREASE" pour définir cnt à 0, mais c'était -1 ("INCREASE" a été ignoré)

Regardez ça : https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154

let listener = () => {
  console.log("test");
};
const middleware = (action) => {
  console.log(action);
  if (action.type === "INCREASE") {
    listener();
  }
};

const counterReducer = (state, action) => {
  middleware(action);
  switch (action.type) {
    case "INCREASE":
      return {
        ...state,
        cnt: state.cnt + action.payload
      };
    case "SUB":
      return {
        ...state,
        cnt: state.cnt - action.payload
      };
    default:
      return state;
  }
};

const Test = () => {
  const { cnt, increase, substract } = useContext(CounterContext);

  useEffect(() => {
    listener = substract;
  });

  return (
    <button
      onClick={() => {
        increase();
      }}
    >
      {cnt}
    </button>
  );
};

{type: "INCREASE", payload: 1}
{type: "SUB", payload: 1}
// expected: cnt: 0
// cnt = -1

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X