web-dev-qa-db-fra.com

Lire l'état initial du magasin dans Redux Reducer

L'état initial dans une application Redux peut être défini de deux manières:

Si vous transmettez l'état initial à votre magasin, comment lisez-vous cet état à partir du magasin et faites-en le premier argument de vos réducteurs?

82
cantera

TL; DR

Sans combineReducers() ou un code manuel similaire, initialState l'emporte toujours sur state = ... Dans le réducteur car le state est passé au réducteur est initialState et n'est pas undefined, donc l'argument ES6 la syntaxe n'est pas appliquée dans ce cas.

Avec combineReducers(), le comportement est plus nuancé. Les réducteurs dont l'état est spécifié dans initialState recevront ce state. Les autres réducteurs recevront undefined et, à cause de cela , ils retiendront l'argument par défaut state = ... Spécifié.

En général, initialState l'emporte sur l'état spécifié par le réducteur. Cela permet aux réducteurs de spécifier les données initiales qui ont un sens pour eux comme arguments par défaut, mais permet également le chargement de données existantes (totalement ou partiellement) lorsque vous hydratez le magasin à partir d'un stockage persistant ou du serveur.

Considérons d’abord le cas où vous n’avez qu’un seul réducteur.
Disons que vous n'utilisez pas combineReducers().

Ensuite, votre réducteur pourrait ressembler à ceci:

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

Maintenant, disons que vous créez un magasin avec cela.

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

L'état initial est zéro. Pourquoi? Parce que le deuxième argument de createStore était undefined. Ceci est le state transmis à votre réducteur pour la première fois. Lorsque Redux est initialisé, il envoie une action "factice" pour remplir cet état. Donc, votre counter réducteur a été appelé avec state égal à undefined. C'est exactement le cas qui "active" l'argument par défaut. Par conséquent, state est maintenant 0 Selon la valeur par défaut state valeur (state = 0). Cet état (0) Sera retourné.

Considérons un scénario différent:

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

Pourquoi est-ce 42, Et pas 0, Cette fois? Parce que createStore a été appelé avec le second argument 42. Cet argument devient le state transmis à votre réducteur avec l'action factice. Cette fois, state n'est pas indéfini (c'est 42!), La syntaxe d'argument par défaut d'ES6 n'a donc aucun effet. Le state est 42 Et 42 Est renvoyé par le réducteur.


Considérons maintenant le cas où vous utilisez combineReducers().
Vous avez deux réducteurs:

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

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

Le réducteur généré par combineReducers({ a, b }) ressemble à ceci:

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

Si nous appelons createStore sans le initialState, il initialisera le state à {}. Par conséquent, state.a Et state.b Seront undefined au moment où ils appelleront a et b réducteurs. Les deux réducteurs a et b recevront undefined sous la forme de leur state arguments, et s'ils spécifient les valeurs state par défaut, celles-ci seront renvoyées. C'est ainsi que le réducteur combiné renvoie un objet d'état { a: 'lol', b: 'wat' } lors du premier appel.

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

Considérons un scénario différent:

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

Maintenant, j'ai spécifié le initialState comme argument de createStore(). L'état retourné par le réducteur combiné combine l'état initial que j'ai spécifié pour le réducteur a avec l'argument par défaut 'wat'. a précisé que b réducteur s’était choisi lui-même.

Rappelons ce que fait le réducteur combiné:

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

Dans ce cas, state a été spécifié pour ne pas retomber à {}. C'était un objet avec le champ a égal à 'horse', Mais sans le champ b. C’est pourquoi le réducteur a a reçu 'horse' En tant que state et l’a volontiers retourné, mais le réducteur b a reçu undefined en tant que state et a donc renvoyé son idée de la valeur par défaut state (dans notre exemple, 'wat'). Voici comment nous obtenons { a: 'horse', b: 'wat' } En retour.


Pour résumer, si vous vous en tenez aux conventions Redux et que vous retournez l’état initial des réducteurs quand ils sont appelés avec undefined en tant qu’argument state (le moyen le plus simple de l’implémenter est de spécifier le state valeur d'argument par défaut ES6), vous allez avoir un comportement utile pour les réducteurs combinés. Ils préféreront la valeur correspondante dans l’objet initialState que vous transmettez à la fonction createStore()), mais si vous n’en avez transmis aucune, ou si le champ correspondant n’est pas défini, l'argument par défaut state spécifié par le réducteur est choisi à la place. Cette approche fonctionne bien car elle fournit l'initialisation et l'hydratation des données existantes, mais permet aux différents réducteurs de réinitialiser leur état si leurs données ne sont pas préservées. Bien sûr, vous pouvez appliquer ce modèle de manière récursive, comme vous pouvez utiliser combineReducers() à plusieurs niveaux, ou même composer manuellement des réducteurs en appelant des réducteurs et en leur donnant la partie pertinente de l'arborescence d'état.

177
Dan Abramov

En un mot: c'est Redux celui qui passe l'état initial aux réducteurs, vous n'avez rien à faire.

Lorsque vous appelez createStore(reducer, [initialState]), vous permettez à Redux de connaître l’état initial à transmettre au réducteur lors de la première action.

La deuxième option que vous mentionnez ne s'applique que dans le cas où vous n'avez pas passé un état initial lors de la création du magasin. c'est à dire.

function todoApp(state = initialState, action)

l'état ne sera initialisé que si aucun état n'a été passé par Redux

5
Emilio Rodriguez

comment lisez-vous cet état dans le magasin et en faites-il le premier argument de vos réducteurs?

combineReducers () fait le travail pour vous. La première façon de l'écrire n'est pas vraiment utile:

const rootReducer = combineReducers({ todos, users })

Mais l’autre équivalent est plus clair:

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

J'espère que cela répond à votre demande (que j'ai comprise comme l'initialisation des réducteurs tout en passant intialState et en retournant cet état)

Voici comment nous procédons (avertissement: copié à partir de code TypeScript).

L’essentiel en est le test if(!state) dans la fonction 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 ) {
            ....
        }
    }
}
0
Bruno Grieder