Zugriff auf den Redux-Status in einem Aktionsersteller?

296

Angenommen, ich habe Folgendes:

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

Und in diesem Aktionsersteller möchte ich auf den globalen Speicherstatus (alle Reduzierungen) zugreifen. Ist es besser das zu tun:

import store from '../store';

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

oder dieses:

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

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

Antworten:

522

Es gibt unterschiedliche Meinungen darüber, ob der Zugriff auf den Status von Aktionserstellern eine gute Idee ist:

  • Der Redux-Ersteller Dan Abramov ist der Ansicht, dass dies eingeschränkt werden sollte: "Die wenigen Anwendungsfälle, in denen ich dies für akzeptabel halte, sind die Überprüfung zwischengespeicherter Daten, bevor Sie eine Anfrage stellen, oder die Überprüfung, ob Sie authentifiziert sind (dh einen bedingten Versand durchführen). Ich denke, dass das Übergeben von Daten wie state.something.itemsin einem Aktionsersteller definitiv ein Anti-Pattern ist und entmutigt wird, weil es den Änderungsverlauf verdeckt: Wenn ein Fehler vorliegt und itemsfalsch ist, ist es schwierig zu verfolgen, woher diese falschen Werte stammen, weil sie sind bereits Teil der Aktion, anstatt direkt von einem Reduzierer als Reaktion auf eine Aktion berechnet zu werden. Tun Sie dies also mit Vorsicht. "
  • Der derzeitige Redux-Betreuer Mark Erikson sagt, dass es in Ordnung ist und sogar dazu ermutigt wird, es getStatein Thunks zu verwenden - deshalb gibt es es . In seinem Blog-Beitrag Idiomatic Redux: Gedanken zu Thunks, Sagas, Abstraktion und Wiederverwendbarkeit erörtert er die Vor- und Nachteile des Zugriffs auf den Status von Aktionserstellern .

Wenn Sie feststellen, dass Sie dies benötigen, sind beide von Ihnen vorgeschlagenen Ansätze in Ordnung. Der erste Ansatz erfordert keine Middleware:

import store from '../store';

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

Sie können jedoch sehen, dass es sich storeum einen Singleton handelt, der aus einem Modul exportiert wird. Wir empfehlen dies nicht, da es viel schwieriger ist, Ihrer App Server-Rendering hinzuzufügen, da Sie in den meisten Fällen auf dem Server pro Anfrage einen separaten Speicher haben möchten . Obwohl dieser Ansatz technisch funktioniert, empfehlen wir nicht, ein Geschäft aus einem Modul zu exportieren.

Aus diesem Grund empfehlen wir den zweiten Ansatz:

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

    dispatch(anotherAction(items));
  }
}

Es würde erfordern, dass Sie Redux Thunk Middleware verwenden, aber es funktioniert sowohl auf dem Client als auch auf dem Server einwandfrei. Lesen Sie hier mehr über Redux Thunk und warum dies in diesem Fall erforderlich ist .

Im Idealfall sollten Ihre Aktionen nicht „fett“ sein und so wenig Informationen wie möglich enthalten. Sie sollten sich jedoch frei fühlen, das zu tun, was für Sie in Ihrer eigenen Anwendung am besten funktioniert. Die Redux-FAQ enthält Informationen zur Aufteilung der Logik zwischen Aktionserstellern und Reduzierungen sowie zu Zeiten, in denen die Verwendung getStatein einem Aktionsersteller hilfreich sein kann .

Dan Abramov
quelle
5
Ich habe eine Situation, in der die Auswahl von Elementen in einer Komponente einen PUT oder einen POST auslösen kann, je nachdem, ob der Speicher Daten enthält oder nicht, die sich auf die Komponente beziehen. Ist es besser, die Geschäftslogik für die PUT / POST-Auswahl in die Komponente zu integrieren, als den Thunk-basierten Aktionsersteller?
Vicusbass
3
Was ist die beste Vorgehensweise? Ich stehe jetzt vor einem ähnlichen Problem, bei dem ich getState in meinem Action Creator verwende. In meinem Fall verwende ich es, um festzustellen, ob Werte für ein Formular ausstehende Änderungen aufweisen (und wenn ja, werde ich eine Aktion auslösen, die einen Dialog anzeigt).
Arne H. Bitubekk
42
Es ist in Ordnung, im Action Creator aus dem Store zu lesen. Ich würde Sie ermutigen, einen Selektor zu verwenden, damit Sie nicht von der genauen Zustandsform abhängen.
Dan Abramov
2
Ich verwende eine Middleware, um Daten an das Mixpanel zu senden. Ich habe also einen Metaschlüssel in der Aktion. Ich muss verschiedene Variablen vom Status an das Mixpanel übergeben. Das Setzen auf die Action-Ersteller scheint ein Anti-Pattern zu sein. Was wäre der beste Ansatz, um diese Art von Anwendungsfällen zu behandeln?
Aakash Sigdel
2
Oh Mann! Ich habe diesen Redux-Thunk-Empfang nicht getStateals zweiten Parameter
geknotet
33

Wenn Ihr Szenario einfach ist, können Sie verwenden

import store from '../store';

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

Aber manchmal action creatormüssen Sie mehrere Aktionen auslösen

Zum Beispiel asynchrone Anforderung, damit Sie REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILAktionen benötigen

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

Hinweis: Sie benötigen Redux-Thunk , um die Funktion im Aktionsersteller zurückzugeben

Nour Sammour
quelle
3
Kann ich nur fragen, ob der Status des Status "Laden" überprüft werden soll, damit keine weitere Ajax-Anfrage gestellt wird, während die erste abgeschlossen ist?
JoeTidee
@JoeTidee Im Beispiel erfolgt der Versand des Ladezustands. Wenn Sie diese Aktion beispielsweise mit der Schaltfläche ausführen, überprüfen Sie, ob sie loading === truevorhanden ist, und deaktivieren Sie die Schaltfläche.
Croraf
4

Ich stimme @Bloomca zu. Das Übergeben des vom Geschäft benötigten Werts an die Versandfunktion als Argument scheint einfacher zu sein als das Exportieren des Geschäfts. Ich habe hier ein Beispiel gemacht:

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);
Jason Allshorn
quelle
Diese Methode ist ziemlich logisch.
Ahmet Şimşek
Dies ist der richtige Weg, und wie ich es mache. Ihr Action-Creator muss nicht den gesamten Status kennen, sondern nur das Bit, das für ihn relevant ist.
Asche
3

Ich möchte darauf hinweisen, dass es nicht so schlecht ist, aus dem Geschäft zu lesen - es ist möglicherweise viel bequemer, zu entscheiden, was basierend auf dem Geschäft getan werden soll, als alles an die Komponente und dann als Parameter von zu übergeben eine Funktion. Ich stimme Dan voll und ganz zu, dass es viel besser ist, store nicht als Singletone zu verwenden, es sei denn, Sie sind zu 100% sicher, dass Sie es nur für clientseitiges Rendern verwenden (andernfalls können schwer nachvollziehbare Fehler auftreten).

Ich habe kürzlich eine Bibliothek erstellt , um die Ausführlichkeit von Redux zu behandeln, und ich denke, es ist eine gute Idee, alles in die Middleware zu integrieren, damit Sie alles als Abhängigkeitsinjektion haben.

Ihr Beispiel sieht also so aus:

import { createSyncTile } from 'redux-tiles';

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

Wie Sie sehen, ändern wir die Daten hier jedoch nicht wirklich. Daher besteht eine gute Chance, dass wir diesen Selektor einfach an einer anderen Stelle verwenden können, um sie an einer anderen Stelle zu kombinieren.

Bloomca
quelle
1

Vorstellung einer alternativen Lösung. Dies kann je nach Anwendung besser oder schlechter sein als die Lösung von Dan.

Sie können den Status von den Reduzierern in die Aktionen übernehmen, indem Sie die Aktion in zwei separate Funktionen aufteilen: erst nach den Daten fragen, zweitens nach den Daten. Sie können dies mit verwenden redux-loop.

Zuerst 'bitte nach den Daten fragen'

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

Fangen Sie im Reduzierer die Abfrage ab und stellen Sie die Daten mithilfe von für die Aktion der zweiten Stufe bereit 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))
        }
    }
}

Machen Sie mit den vorliegenden Daten, was Sie ursprünglich wollten

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

Hoffe das hilft jemandem.

Andrei Cioara
quelle
0

Ich weiß, dass ich zu spät zur Party hier komme, aber ich bin hierher gekommen, um Meinungen über meinen eigenen Wunsch, Staat in Handlungen zu verwenden, zu äußern, und habe mich dann selbst gebildet, als mir klar wurde, was ich für das richtige Verhalten halte.

Hier macht ein Selektor für mich am meisten Sinn. Ihrer Komponente, die diese Anforderung ausgibt, sollte mitgeteilt werden, ob es Zeit ist, sie durch Auswahl auszugeben.

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

Es mag sich wie undichte Abstraktionen anfühlen, aber Ihre Komponente muss eindeutig eine Nachricht senden und die Nachrichtennutzlast sollte den relevanten Status enthalten. Leider hat Ihre Frage kein konkretes Beispiel, da wir auf diese Weise ein „besseres Modell“ von Selektoren und Aktionen durcharbeiten könnten.

SqueeDee
quelle
0

Ich möchte noch eine andere Alternative vorschlagen, die ich am saubersten finde, aber sie erfordert react-reduxoder etwas Ähnliches - außerdem verwende ich unterwegs ein paar andere ausgefallene Funktionen:

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

Wenn Sie diese wieder verwenden möchten , müssen Sie die spezifische extrahieren müssen mapState, mapDispatchund mergePropsin Funktionen an anderer Stelle wieder zu verwenden, aber das macht Abhängigkeiten vollkommen klar.

Sam96
quelle