web-dev-qa-db-fra.com

Accéder à l'état Redux dans un créateur d'action?

Dites que j'ai ce qui suit:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

Et dans ce créateur d'action, je souhaite accéder à l'état du magasin global (tous les réducteurs). Est-il préférable de faire ceci:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

ou ca:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}
244
ffxsam

Les opinions divergent quant à savoir si l'accès aux créateurs d'état dans l'action est une bonne idée:

  • Le créateur de Redux, Dan Abramov, estime que cela devrait être limité: "Les quelques cas d'utilisation acceptables sont ceux qui permettent de vérifier les données en cache avant de faire une demande ou de vérifier si vous êtes authentifié (en d'autres termes, une dépêche conditionnelle). Je pense que le fait de transmettre des données telles que state.something.items dans un créateur d'action est définitivement un anti-motif et est déconseillé car il occultait l'historique des modifications: s'il y a un bogue et que items sont incorrects, il est difficile de retracer d'où proviennent ces valeurs incorrectes car elles font déjà partie de l'action plutôt que d'être calculées directement par un réducteur en réponse à une action. Faites-le avec précaution. "
  • Le responsable actuel de Redux, Mark Erikson, dit que c'est bien et même encouragé d'utiliser getState dans thunks - c'est pourquoi il existe . Il discute des avantages et des inconvénients de l'accès à l'état en action des créateurs dans son billet de blog Idiomatic Redux: réflexions sur les thunks, les sagas, l'abstraction et la réutilisabilité .

Si vous trouvez que vous en avez besoin, les deux approches que vous avez suggérées sont acceptables. La première approche ne nécessite aucun middleware:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Cependant, vous pouvez voir que cela dépend de store étant un singleton exporté à partir d'un module. Nous ne recommandons pas cette option , car il est beaucoup plus difficile de ajouter le rendu du serveur à votre application car dans la plupart des cas sur le serveur, vous souhaiterez avoir un magasin séparé par demande . Donc, bien que techniquement cette approche fonctionne, nous ne recommandons pas d’exporter un magasin à partir d’un module.

C'est pourquoi nous recommandons la deuxième approche:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

Cela nécessiterait que vous utilisiez le middleware Redux Thunk mais cela fonctionne très bien sur le client et sur le serveur. Vous pouvez en savoir plus sur Redux Thunk et pourquoi il est nécessaire dans ce cas ici .

Idéalement, vos actions ne devraient pas être "grasses" et contenir le moins d’informations possible, mais vous devriez vous sentir libre de faire ce qui vous convient le mieux dans votre propre application. Le Redux FAQ a des informations sur division de la logique entre les créateurs d’action et les réducteurs et moments où il peut être utile d’utiliser getState dans un créateur d’action =.

436
Dan Abramov

Lorsque votre scénario est simple, vous pouvez utiliser

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Mais parfois, votre action creator doit déclencher plusieurs actions

par exemple, une requête asynchrone nécessite donc des actions REQUEST_LOADREQUEST_LOAD_SUCCESSREQUEST_LOAD_FAIL

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

Remarque: vous avez besoin de redux-thunk pour renvoyer une fonction dans le créateur de l'action

27
Nour Sammour

Je tiens à préciser qu’il n’est pas si mal de lire dans le magasin - il serait peut-être beaucoup plus pratique de décider ce qui doit être fait en fonction du magasin, que de tout transférer au composant, puis en tant que paramètre de une fonction. Je suis tout à fait d’accord avec Dan pour dire qu’il est bien préférable de ne pas utiliser store en tant que singletone, sauf si vous êtes sûr à 100% de l’utiliser uniquement pour le rendu côté client (sinon, des bugs difficiles à détecter pourraient apparaître).

J'ai créé une bibliothèque récemment pour traiter la verbosité de redux, et je pense que c'est une bonne idée de tout mettre dans le middleware, afin que vous ayez chaque chose comme injection de dépendance.

Alors, votre exemple ressemblera à ça:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

Cependant, comme vous pouvez le constater, nous ne modifions pas vraiment les données ici. Il y a donc de fortes chances pour que nous puissions simplement utiliser ce sélecteur ailleurs pour le combiner ailleurs.

3
Bloomca

Je suis d'accord avec @Bloomca. Transmettre la valeur nécessaire du magasin à la fonction de répartition en tant qu'argument semble plus simple que d'exporter le magasin. J'ai fait un exemple ici:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);
2
Jason Allshorn

Présenter un autre moyen de résoudre ce problème. Cela peut être meilleur ou pire que la solution de Dan, selon votre application.

Vous pouvez obtenir l'état des réducteurs dans les actions en fractionnant l'action en deux fonctions distinctes: demander d'abord les données, ensuite agir sur les données. Vous pouvez le faire en utilisant redux-loop .

D'abord, merci de demander les données.

_export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}
_

Dans le réducteur, interceptez la demande et fournissez les données à la deuxième étape en utilisant _redux-loop_.

_import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}
_

Avec les données en main, faites ce que vous vouliez au départ

_export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}
_

J'espère que ça aide quelqu'un.

1
Andrei Cioara

Je sais que je suis en retard à la fête ici, mais je suis venu ici pour des opinions sur mon propre désir d'utiliser l'État dans des actions, puis j'ai formé le mien, quand j'ai réalisé ce que je pensais être le comportement correct.

C'est là qu'un sélecteur me semble le plus logique. Votre composant qui émet cette demande doit être informé du moment opportun pour l’émettre par sélection.

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

Cela peut sembler être une fuite d’abstractions, mais votre composant doit clairement envoyer un message et le contenu du message doit contenir l’état pertinent. Malheureusement, votre question ne contient pas d'exemple concret, car nous pourrions travailler sur un "meilleur modèle" de sélecteurs et d'actions de cette manière.

0
SqueeDee