Accessing other parts of the state, when using com

2019-02-06 00:26发布

问题:

NB: This is a question, very similar to Redux; accessing other parts... but it has nothing to do with Router :( thus cannot be solved same way

When I reduce one part of the state, I "feel" like I need to access other parts as well. I admit that I just might "misfeel" the core principals of the Redux, or have flaws in my app's architecture.

My current solution would be to modify github:combineReducers.js's code:

var finalState = mapValues(finalReducers, (reducer, key) => {
   var previousStateForKey = state[key]
   var nextStateForKey = reducer(previousStateForKey, action)
   ...
}

from

   var nextStateForKey = reducer(previousStateForKey, action)

to

   var nextStateForKey = reducer(previousStateForKey, action, state)

which would allow me to do what I need:

function reducer(state, action, root) {
   if (root.otherPart.get("isSomething")) {
      return state.set("anotherThing", true);
   }
   return state;
}

Question is if I am on the right way to do it, or is it something that should be solved using different architectural approaches, without having a need to access one part of state from other parts?

** UPDATE 5h Dec, 2018 **

Due to relatively high interest to this question (15 up-votes atm), I am adding my own answer below, hope it helps to those who is looking for that answer.

回答1:

You don't have to use combineReducers().

From the official Redux docs:

This helper is just a convenience! You can write your own combineReducers that works differently, or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function.

You may call combineReducers at any level of the reducer hierarchy. It doesn’t have to happen at the top. In fact you may use it again to split the child reducers that get too complicated into independent grandchildren, and so on.

So your proposed solution is definitely one way you can do it.

Thanks to @MichelleTilley for spearheading the train of thought!



回答2:

I use Thunk to getState(), prepare the data from the full store, then dispatch an action.

You can put the decision logic in the reducer or in the action. It is up to you. I prefer fat actions and thin reducers but there is no right/wrong.

Leonardo



回答3:

You can try to use:

redux-named-reducers

Which allows you to get state anywhere in your code like so:

const localState1 = getState(reducerA.state1)
const localState2 = getState(reducerB.state2)

Works with combineReducers such that each reducer only handles local state but can access external state if needed.



回答4:

Intro

I'd like to add an answer, which is based on my 3y+ experience and also recaps some of the other answers and comments above (Thanks to all the contributors)

Short Answer

An original combineReducers can be used. Every selector works with Root State. Reducer mutates only it's part of the Root State. Reducer is as thin as possible, and only Action (or Thunk) is responsible for collecting all the data, needed to reduce the part of the Root State

Detailed Answer

Application's reducers, actions and selectors are organised using Ducks approach. Using Redux Thunk gives lot of benefits (thanks to Leonardo).

If we use definitions of my initial question, one single "Duck" - is "one part of the state", and it groups following major items:

  • Reducer (usually a function named %duckName%, receives )
  • Actions and Thunks
  • Selectors (which receive root state)
  • Types (type, interface, enums)

State of the application - is an object. It's keys - are the names of all the "Ducks" that participate in reducing application's state.

Reducers shall be kept as thin as possible, as Leonardo has advised.

All of the Actions and Thunks, can be split into following categories:

  1. Duck Action
    • no selectors inside
    • arguments have all the info, which is sufficient for independent work
    • returns Action object { type: "%actionName%", payload: ... }
  2. Duck Thunk
    • no external selectors, but can have internal ones (selecting from the same Duck)
    • does dispatch of one or more Duck Actions.
    • might dispatch other Duck Thunks, but shall be avoided, as it leads to high complexity of the application.
  3. Smart Duck Thunk
    • is aware of other Ducks existence, thus might select from other Ducks
    • shall not dispatch to other Ducks, as in this case, it might considered as Application Action
  4. Application Action
    • returns Duck Action or other Application Action with modified arguments
  5. Application Thunk
    • have selectors from any of the ducks
    • might dispatch any of the Thunk or Action

In some cases, you can have more than one Application in your codebase. Your Applications might share some of the Ducks. Sometimes you need to create Duck for every Application, so you can pass some configuration. But these are puzzles of a different story :)

I've tried to cover minimal portion of them in this repository, which I intend to keep contributing to. It's written using Typescript. I hope it makes it more easier to understand