Accedere allo stato Redux in un creatore di azioni?


296

Di 'che ho il seguente:

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

E in quel creatore di azioni, voglio accedere allo stato globale del negozio (tutti i riduttori). È meglio farlo:

import store from '../store';

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

o questo:

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

    dispatch(anotherAction(items));
  }
}

Risposte:


522

Esistono opinioni divergenti sull'opportunità di accedere allo stato nei creatori di azioni:

  • Il creatore di Redux Dan Abramov ritiene che dovrebbe essere limitato: "I pochi casi d'uso in cui ritengo sia accettabile è quello di verificare i dati memorizzati nella cache prima di effettuare una richiesta o di verificare se si è autenticati (in altre parole, effettuare una spedizione condizionale). Penso che il passaggio di dati come state.something.itemsin un creatore di azioni sia sicuramente un anti-pattern ed è scoraggiato perché ha oscurato la cronologia delle modifiche: se c'è un bug e itemssono errati, è difficile rintracciare da dove provengono quei valori errati perché sono già parte dell'azione, piuttosto che calcolata direttamente da un riduttore in risposta a un'azione. Fallo con cura ".
  • L'attuale manutentore di Redux Mark Erikson afferma che va bene e persino incoraggiato a usarlo getStatein thunk - ecco perché esiste . Discute i pro e i contro dell'accesso allo stato nei creatori di azioni nel suo post sul blog Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction e Reusability .

Se scopri di averne bisogno, entrambi gli approcci che hai suggerito vanno bene. Il primo approccio non richiede alcun middleware:

import store from '../store';

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

Tuttavia puoi vedere che si basa storesull'essere un singleton esportato da alcuni moduli. Non è consigliabile perché rende molto più difficile aggiungere il rendering del server alla tua app perché nella maggior parte dei casi sul server vorrai avere un negozio separato per richiesta . Quindi, sebbene tecnicamente questo approccio funzioni, non è consigliabile esportare un negozio da un modulo.

Questo è il motivo per cui raccomandiamo il secondo approccio:

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

    dispatch(anotherAction(items));
  }
}

Richiederebbe l'uso del middleware Redux Thunk ma funziona bene sia sul client che sul server. Puoi leggere di più su Redux Thunk e perché è necessario in questo caso qui .

Idealmente, le tue azioni non dovrebbero essere "grasse" e dovrebbero contenere il minor numero di informazioni possibile, ma dovresti sentirti libero di fare ciò che funziona meglio per te nella tua applicazione. Le FAQ di Redux contengono informazioni sulla suddivisione della logica tra i creatori di azioni e i riduttori e i momenti in cui può essere utile utilizzarli getStatein un creatore di azioni .


5
Ho una situazione quando la selezione di qualcosa in un componente potrebbe innescare un PUT o un POST, a seconda che il negozio contenga o meno dati relativi al componente. È meglio inserire la logica aziendale per la selezione PUT / POST nel componente anziché nel creatore di azioni basato su thunk?
vicusbass,

3
Qual è la migliore pratica? Ora sto affrontando il problema simile in cui sto usando getState nel mio creatore di azioni. Nel mio caso lo uso per determinare se i valori se un modulo ha modifiche in sospeso (e in tal caso invierò un'azione che mostra una finestra di dialogo).
Arne H. Bitubekk,

42
Va bene leggere dal negozio nel creatore dell'azione. Ti incoraggio a utilizzare un selettore in modo da non dipendere dalla forma esatta dello stato.
Dan Abramov,

2
Sto usando un middleware per inviare dati a mixpanel. Quindi ho una meta chiave all'interno dell'azione. Devo passare diverse variabili dallo stato al pannello di controllo. Impostarli sui creatori di azioni sembra essere un anti-schema. Quale sarebbe l'approccio migliore per gestire questo tipo di casi d'uso?
Aakash Sigdel,

2
Oddio! Non ho getState
annodato quel Redux Thunk

33

Quando il tuo scenario è semplice, puoi usarlo

import store from '../store';

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

Ma a volte è action creatornecessario attivare più azioni

ad esempio una richiesta asincrona, quindi sono necessarie REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILazioni

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

Nota: è necessario redux-thunk per restituire la funzione nel creatore di azioni


3
Posso solo chiedere se dovrebbe esserci un controllo sullo stato dello stato 'caricamento' in modo che non venga fatta un'altra richiesta Ajax mentre la prima sta finendo?
Joe Tidee,

@JoeTidee Nell'esempio l'invio dello stato di caricamento è fatto. Se si esegue questa azione con il pulsante, ad esempio, si controlla se è loading === truepresente e si disabilita il pulsante.
croraf,

4

Sono d'accordo con @Bloomca. Passare il valore necessario dal negozio alla funzione di invio come argomento sembra più semplice dell'esportazione del negozio. Ho fatto un esempio qui:

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

Questo metodo è abbastanza logico.
Ahmet Şimşek,

Questo è il modo corretto per farlo e come lo faccio. Il tuo creatore di azioni non ha bisogno di conoscere l'intero stato, solo il bit che è rilevante per esso.
Ash,

3

Vorrei sottolineare che non è così male leggere dal negozio - potrebbe essere molto più conveniente decidere cosa fare in base al negozio, piuttosto che passare tutto al componente e quindi come parametro di una funzione. Sono completamente d'accordo con Dan sul fatto che è molto meglio non usare store come un singolo, a meno che non si sia sicuri al 100% che si utilizzerà solo per il rendering sul lato client (altrimenti potrebbero apparire bug difficili da rintracciare).

Di recente ho creato una libreria per gestire la verbosità del redux e penso che sia una buona idea mettere tutto nel middleware, in modo da avere tutto come iniezione di dipendenza.

Quindi, il tuo esempio sarà simile al seguente:

import { createSyncTile } from 'redux-tiles';

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

Tuttavia, come puoi vedere, non modifichiamo davvero i dati qui, quindi c'è una buona possibilità che possiamo semplicemente usare questo selettore in un altro posto per combinarli altrove.


1

Presentare un modo alternativo di risolverlo. Questo potrebbe essere migliore o peggiore della soluzione di Dan, a seconda della tua applicazione.

È possibile ottenere lo stato dai riduttori nelle azioni suddividendo l'azione in 2 funzioni separate: prima chiedere i dati, secondo agire sui dati. Puoi farlo usando redux-loop.

Prima "chiedi gentilmente i dati"

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

Nel riduttore, intercettare la richiesta e fornire i dati all'azione del secondo stadio utilizzando 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))
        }
    }
}

Con i dati in mano, fai quello che inizialmente volevi

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

Spero che questo aiuti qualcuno.


0

So di essere in ritardo alla festa qui, ma sono venuto qui per opinioni sul mio desiderio di usare lo stato nelle azioni, e poi ho formato il mio, quando ho realizzato quello che penso sia il comportamento corretto.

È qui che un selettore ha più senso per me. Il componente che emette questa richiesta deve essere informato se è il momento di emetterlo tramite la selezione.

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

Potrebbe sembrare una perdita di astrazioni, ma il componente deve chiaramente inviare un messaggio e il payload del messaggio deve contenere lo stato pertinente. Sfortunatamente la tua domanda non ha un esempio concreto perché potremmo elaborare un "modello migliore" di selettori e azioni in quel modo.


0

Vorrei suggerire un'altra alternativa che trovo la più pulita, ma richiede react-reduxo qualcosa di simile - inoltre sto usando alcune altre fantasiose funzionalità lungo la strada:

// actions.js
export const someAction = (items) => ({
    type: 'SOME_ACTION',
    payload: {items},
});
// Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction}) => (<div
    onClick={boundSomeAction}
/>);

const mapState = ({otherReducer: {items}}) => ({
    items,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with  other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
    onClick={boundSomeAction}
    onSomeOtherEvent={otherAction}
>
    {JSON.stringify(otherMappedState)}
</div>);

const mapState = ({otherReducer: {items}, otherMappedState}) => ({
    items,
    otherMappedState,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
    otherAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    const {items, ...remainingMappedState} = mappedState;
    const {someAction, ...remainingMappedDispatch} = mappedDispatch;
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => someAction(items),
        ...remainingMappedState,
        ...remainingMappedDispatch,
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);

Se si desidera riutilizzare questo dovrete estrarre lo specifica mapState, mapDispatche mergePropsin funzioni di riutilizzare altrove, ma questo rende le dipendenze perfettamente chiaro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.