L'état initial dans une application Redux peut être défini de deux manières:
createStore
( lien de la documentation )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?
TL; DR
Sans
combineReducers()
ou un code manuel similaire,initialState
l'emporte toujours surstate = ...
Dans le réducteur car lestate
est passé au réducteur estinitialState
et n'est pasundefined
, 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é dansinitialState
recevront cestate
. Les autres réducteurs recevrontundefined
et, à cause de cela , ils retiendront l'argument par défautstate = ...
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.
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
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)
}
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 ) {
....
}
}
}