NgRx - How are states combined and initialized

2019-05-21 05:28发布

When we initialize our Store:

StoreModule.provideStore({r1: Reducer1, r2: Reducer2, ...})

we do pass the reducers to the Store to be stored. But we never actually pass the initial states to the store, except defining it in the reducers functions:

const someReducer = (state = initialState, act: Action) => { ... }

SO, is it that when application bootstraps, all the reducers are called once to acquire the initial state from reducer definition, and then store the state in the NgRx Store? If so, is it that every reducer must have an initial state value? otherwise the states will always be undefined?

And, if all reducers are called at bootstrap, how does NgRx make sure that the reduce must get to the default case?:

case ...:
   ...
default:
   return initialState

Thanks a lot! Any help appreciated!!

1条回答
别忘想泡老子
2楼-- · 2019-05-21 06:12

Every time an action is dispatched to the store, every registered reducer is called. A reducer is just a function that takes the current state plus an action and returns a new state.

So yes, on bootstrap an init-action is dispatched (and therefore every reducer is called). If you use store-devtools for example, you will see that the initial action is called @ngrx/store/init. Since the current state is undefined at this point, the reducer-function is called with the following: someReducer(undefined, { type: '@ngrx/store/init' })

When a function gets called with a parameter that is undefined and it has a parameter with a default value, it will use that default value instead.

function funcWithoutDefault(myParam) {
    console.log(myParam);
}

funcWithoutDefault('hello'); // 'hello'
funcWithoutDefault(undefined); // undefined

function funcWithDefault(myParam = 'world') {
    console.log(myParam);
}

funcWithDefault('hello'); // 'hello'
funcWithDefault(undefined); // 'world'

That's probably nothing new to you. But hopefully it helps to answer your question about initial state:

As I said, the current state is undefined before the init-action is called. So when the reducer-function has a parameter with a default value (the initialstate), the store will be initialized with this initial state. If you don't define a default value for a reducer, that slice of the state stays undefined. This could be a valid scenario. Consider for example a reducer that handles actions for an admin-section. Unless a user with elevated rights uses your application, you might not want to have anything in that state-slice.

Now to the point where I presume your question came from:

You don't want to return the initialState in the default case! Since every reducer gets called on every action-dispatch, the default case should return state. The store doesn't know what reducer an action is meant for. It calls every reducer with the current state and the dispatched action and expects to get a (new) state back.
If a certain reducer should react to an action is up to you. Every other reducer that should not react to this action should return it's unaltered state (via the default case). And of course you can have multiple reducers react to the same action!

In your case, if every reducer returns initialState in the default case your whole app-state would reset to it's inital state, except for the reducer that reacts to the dispatched action.

TL;DR for your questions:

  • Yes, all reducer-functions get called on bootstrap (and on every other action dispatch)...
  • ... and yes, that is when the store gets initialized with the initial state.
  • No, a reducer must not always have an initial state*
  • If a slice of state is initially undefined, it can still be changed by the reducer reacting to an action by returning a new state.
  • Every reducer not reacting to an action should return the current state via the default-case.

* of course this only makes sense when the rest of your application is able to handle an undefined state

查看更多
登录 后发表回答