Lesen Sie den Anfangszustand des Stores in Redux Reducer

84

Der Anfangszustand in einer Redux-App kann auf zwei Arten festgelegt werden:

  • Übergeben Sie es als zweites Argument an createStore( docs link )
  • Übergeben Sie es als erstes Argument an Ihre (Unter-) Reduzierer ( docs link )

Wenn Sie den Anfangszustand an Ihr Geschäft übergeben, wie lesen Sie diesen Zustand aus dem Geschäft und machen ihn zum ersten Argument in Ihren Reduzierungen?

Cantera
quelle

Antworten:

184

TL; DR

Ohne combineReducers()oder ähnlichen manuellen Code initialStategewinnt immer state = ...im Reduzierer, weil der statean den Reduzierer übergebene ist initialState und nicht undefined , so dass die ES6-Argumentsyntax in diesem Fall nicht angewendet wird.

Mit combineReducers()dem Verhalten wird nuancierter. Diejenigen Reduzierer, deren Zustand in angegeben initialStateist, erhalten dies state. Andere Reduzierer erhalten undefined und greifen daher auf das von state = ...ihnen angegebene Standardargument zurück.

Im Allgemeinen initialStategewinnt der vom Reduzierer angegebene Zustand. Auf diese Weise können Reduzierer Anfangsdaten angeben, die für sie als Standardargumente sinnvoll sind , aber auch vorhandene Daten (ganz oder teilweise) laden, wenn Sie den Speicher von einem dauerhaften Speicher oder vom Server aus hydratisieren.

Betrachten wir zunächst einen Fall, in dem Sie einen einzelnen Reduzierer haben.
Angenommen, Sie verwenden nicht combineReducers().

Dann könnte Ihr Reduzierer so aussehen:

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT': return state + 1;
  case 'DECREMENT': return state - 1;
  default: return state;
  }
}

Angenommen, Sie erstellen damit ein Geschäft.

import { createStore } from 'redux';
let store = createStore(counter);
console.log(store.getState()); // 0

Der Ausgangszustand ist Null. Warum? Weil das zweite Argument createStorewar undefined. Dies wird statebeim ersten Mal an Ihren Reduzierer übergeben. Wenn Redux initialisiert wird, wird eine "Dummy" -Aktion ausgelöst, um den Status zu füllen. Also wurde Ihr counterReduzierer mit stategleich aufgerufen undefined. Dies ist genau der Fall, der das Standardargument „aktiviert“. Daher entspricht statejetzt 0der Standardwert state( state = 0). Dieser Zustand ( 0) wird zurückgegeben.

Betrachten wir ein anderes Szenario:

import { createStore } from 'redux';
let store = createStore(counter, 42);
console.log(store.getState()); // 42

Warum ist es diesmal 42nicht und nicht 0? Da createStorewurde mit 42als zweites Argument aufgerufen . Dieses Argument wird statezusammen mit der Dummy-Aktion an Ihren Reduzierer übergeben. Dieses Mal stateist nicht undefiniert (es ist 42!), Daher hat die ES6-Standardargumentsyntax keine Auswirkung. Das stateist 42und 42wird vom Reduzierstück zurückgegeben.


Betrachten wir nun einen Fall, in dem Sie verwenden combineReducers().
Sie haben zwei Reduzierungen:

function a(state = 'lol', action) {
  return state;
}

function b(state = 'wat', action) {
  return state;
}

Der von erzeugte Reduzierer combineReducers({ a, b })sieht folgendermaßen aus:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

Wenn wir anrufen , createStoreohne das initialState, es wird die initialisieren statezu {}. Daher state.aund state.bwird es zu undefineddem Zeitpunkt sein, an dem es anruft aund breduziert. Sowohl aund bReduzierungen erhalten undefinedals ihre state Argumente, und wenn sie angeben , Standardwerte,state werden diese zurückgeführt werden. Auf diese Weise gibt der kombinierte Reduzierer { a: 'lol', b: 'wat' }beim ersten Aufruf ein Statusobjekt zurück .

import { createStore } from 'redux';
let store = createStore(combined);
console.log(store.getState()); // { a: 'lol', b: 'wat' }

Betrachten wir ein anderes Szenario:

import { createStore } from 'redux';
let store = createStore(combined, { a: 'horse' });
console.log(store.getState()); // { a: 'horse', b: 'wat' }

Jetzt habe ich das initialStateals Argument angegeben createStore(). Der vom kombinierten Reduzierer zurückgegebene Status kombiniert den für den aReduzierer angegebenen Anfangszustand mit dem 'wat'Standardargument, das der bReduzierer selbst ausgewählt hat.

Erinnern wir uns, was der kombinierte Reduzierer tut:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

In diesem Fall statewurde angegeben, dass nicht darauf zurückgegriffen wurde {}. Es war ein Objekt mit einem aFeld gleich 'horse', aber ohne bFeld. Dies ist der Grund, warum der aReduzierer 'horse'als sein empfangen stateund es gerne zurückgegeben hat, aber der bReduzierer undefinedals sein empfangen hat stateund somit seine Idee des Standards zurückgegeben hat state(in unserem Beispiel 'wat'). So kommen wir { a: 'horse', b: 'wat' }zurück.


Um dies zusammenzufassen: Wenn Sie sich an Redux-Konventionen halten und den Anfangszustand von Reduzierern zurückgeben, wenn diese undefinedals stateArgument aufgerufen werden (der einfachste Weg, dies zu implementieren, besteht darin, den stateES6-Standardargumentwert anzugeben ) ein schönes nützliches Verhalten für kombinierte Reduzierstücke. Sie bevorzugen den entsprechenden Wert in dem initialStateObjekt, das Sie an die createStore()Funktion übergeben. Wenn Sie jedoch keinen übergeben haben oder wenn das entsprechende Feld nicht festgelegt ist, wird der Standardwert verwendet.state ist, wird stattdessen das vom Reduzierer angegebene ausgewählt.Dieser Ansatz funktioniert gut, da er sowohl die Initialisierung als auch die Hydratisierung vorhandener Daten ermöglicht, es jedoch einzelnen Reduzierern ermöglicht, ihren Status zurückzusetzen, wenn ihre Daten nicht erhalten wurden. Natürlich können Sie dieses Muster rekursiv anwenden, da Sie es combineReducers()auf vielen Ebenen verwenden können, oder sogar Reduzierungen manuell erstellen, indem Sie Reduzierungen aufrufen und ihnen den relevanten Teil des Statusbaums zuweisen.

Dan Abramov
quelle
3
Vielen Dank für das tolle Detail - genau das, wonach ich gesucht habe. Die Flexibilität Ihrer API ist hier hervorragend. Der Teil, den ich nicht gesehen habe, ist, dass der "Kind" -Reduzierer anhand des in verwendeten Schlüssels versteht, welcher Teil des Anfangszustands dazu gehört combineReducers. Nochmals vielen Dank.
Cantera
Danke für diese Antwort, Dan. Wenn ich Ihre Antwort richtig verdaue, sagen Sie, dass es am besten ist, einen Anfangszustand über den Aktionstyp eines Reduzierers festzulegen? Beispiel: GET_INITIAL_STATE und Aufruf eines Versands an diesen Aktionstyp in einem Rückruf von componentDidMount?
Con Antonakos
@ConAntonakos Nein, das meine ich nicht. Ich meine, Sie wählen den Ausgangszustand genau dort, wo Sie den Reduzierer definieren, z . B. function counter(state = 0, action)oder function visibleIds(state = [], action).
Dan Abramov
1
@DanAbramov: Wie kann ich mithilfe des zweiten Arguments in createstore () den Status beim erneuten Laden der SPA-Seite mit dem zuletzt gespeicherten Status in Redux anstelle der Anfangswerte rehydrieren? Ich frage mich wohl, wie ich den gesamten Speicher zwischenspeichern und beim Neuladen beibehalten und an die Createstore-Funktion übergeben kann.
Jason
1
@jasan: Verwenden Sie localStorage. Egghead.io/lessons/…
Dan Abramov
5

Kurz gesagt: Es ist Redux, der den Ausgangszustand an die Reduzierer weitergibt. Sie müssen nichts tun.

Wenn Sie anrufen createStore(reducer, [initialState]), teilen Sie Redux mit, welcher Ausgangszustand an den Reduzierer übergeben werden soll, wenn die erste Aktion eingeht.

Die zweite Option, die Sie erwähnen, gilt nur für den Fall, dass Sie beim Erstellen des Geschäfts keinen Anfangszustand überschritten haben. dh

function todoApp(state = initialState, action)

Der Status wird nur initialisiert, wenn kein Status von Redux übergeben wurde

Emilio Rodriguez
quelle
1
Vielen Dank für Ihre Antwort. Wenn Sie im Fall der Reduzierungszusammensetzung den Anfangszustand auf das Geschäft angewendet haben, wie können Sie dem untergeordneten / untergeordneten Reduzierer mitteilen, welchen Teil des Anfangszustandsbaums er besitzt? Wenn Sie beispielsweise eine haben menuState, wie kann ich den Menü-Reduzierer anweisen, den Wert von state.menuaus dem Geschäft zu lesen und diesen als Ausgangszustand zu verwenden?
Cantera
Danke, aber meine Frage muss unklar sein. Ich werde abwarten, ob andere antworten, bevor ich sie bearbeite.
Cantera
1

Wie können Sie diesen Status aus dem Geschäft lesen und ihn zum ersten Argument in Ihren Reduzierungen machen?

kombinReduzierer () erledigen den Job für Sie. Der erste Weg, es zu schreiben, ist nicht wirklich hilfreich:

const rootReducer = combineReducers({ todos, users })

Aber der andere, der gleichwertig ist, ist klarer:

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}
Nicolas
quelle
0

Ich hoffe, dies beantwortet Ihre Anfrage (die ich als Initialisieren von Reduzierungen beim Übergeben von intialState und Zurückgeben dieses Status verstanden habe).

So machen wir das (Warnung: aus Typescript-Code kopiert).

Das Wesentliche ist der if(!state)Test in der Funktion mainReducer (Factory)

function getInitialState(): MainState {

     return {
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     }
}



const reducer = combineReducers(
    {
        main:     mainReducer( getInitialState() ),
        ...
    }
)



const mainReducer = ( initialState: MainState ): Reducer => {

    return ( state: MainState, action: Action ): MainState => {

        if ( !state ) {
            return initialState
        }

        console.log( 'Main reducer action: ', action ) 

        switch ( action.type ) {
            ....
        }
    }
}
Bruno Grieder
quelle