I don’t think Redux is very friendly for beginners, many concepts are not very easy to understand, and it is necessary to read the source code after using Redux
createStore
Redux’s Store stores the application’s state tree, which can be changed only through the Dispatch () method.
Although the documentation clearly states that there can only be one store, I work on projects where there may be more than one store, such as multi-page applications.
parameter
CreateStore has three parameters, which are Reducer, preloadedState and enhancer.
reducer
. This is the incomingreducer
Usually throughcombineReducers
Integration of reducerpreloadedState
. When initializing the Store, it is necessary to pass in an initial state, both to give the app page an initial value and to let yourself or others know the state structure of the entire app.enhancer
. It’s usually just some middleware for Redux, and the concept of middleware is a little convoluted.
CreateStore does a parameter check and type check, and the reducer is not required except that it was passed. The forms passed in include the following.
createStore(reducer)
createStore(reducer, enhancer)
createStore(reducer, preloadedState)
createStore(reducer, preloadedState, enhaner)
Internal implementation
CreateStore internally contains three important variables and four methods that can be called externally.
variable
currentState
. Stores the state tree of the entire store management.currentListeners
. Story is actuallyObserver modelA practice of. We can think of currentListeners as a list of functions to execute after state changes.isDispatching
. Is it ongoing?dispatch
.nextListners
. When doingsubscribe
To do this, push the new listener function tonextListners
Array as a snapshot of the latest Listener array.
methods
- GetState Specifies the only way to access state inside a store. Method directly
currentState
Internal variables return directly.
function getState() {
return currentState;
}
Copy the code
- Subscribe is used to add a listener to
nextListners
. For everydispatch
Method,nextListenrs
replacecurrentListeners
, the incoming listener will be executed. Normally we pass in a UI render as a subscribe listener, and every time the state changes, redux tells the UI to render. The listener does not see every change in all states, because states may change several times in dispatch before the listener is executed.subscribe
Method returns a function closure as an unsubscribe.
function subscribe() {
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if(! isSubscribed) {return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)}}Copy the code
- The only way to change state outside of Dispatch. Call Reducer to get the latest state(reducer is the function that builds the state tree) and execute each listener method. Dispatch can only be delivered
plain object
Action (plain JavaScript object) if you want to pass onethunk, requires middleware, redux-thunk.
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// Replace currentListeners with nextListeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
Copy the code
- ReplaceReducer Replace the reducer method. This method is rarely used.
combineReducers
Combine multiple reducers into one function. CombineReducers will call each reducer and merge the state returned by each reducer into the state tree.
parameter
The type is an object. Each key must correspond to a Reducer function.
const params = {
key1: reducerfunc1,
key2: reducerfunc2,
};
Copy the code
Assume that the state form returned by reducerFunc1 and ReducerFunc2 are respectively
const state1 = {
v1: ' ',
v2: 0,
};
const state2 = [];
Copy the code
So our state tree looks like this, and we get the state object from getState as follows.
const state = {
key1: {
v1: ' '.v2: 0,},key2: [],}Copy the code
Internal implementation
The code before the closure is reducer checks
- Filter reducers that are not function reducers (each key must correspond to a Reducer method)
- Check whether each Reducer method has initState and whether the default type returns state
The code is easy to understand.
export default function combineReducers(reducers) {
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
Reducer state before the call
const previousStateForKey = state[key]
// State after the call
const nextStateForKey = reducer(previousStateForKey, action)
// Update the state tree snapshot
nextState[key] = nextStateForKey
// State Indicates whether there is a changed flag. If the Reducer execution results in a state change, a new state object is returned, so we can directly compare the object to determine whether there is a change.hasChanged = hasChanged || nextStateForKey ! == previousStateForKey }return hasChanged ? nextState : state
}
}
Copy the code
applyMiddleware
Middleware, I think, is one of the more interesting and slightly difficult parts of redux as a whole.
You can view the documentation for the middleware on the official website. Middleware can be thought of as doing something before and after a Dispatch method. It can also be likened to Java facets.
export default function applyMiddleware(. middlewares) {
return createStore= >(... args) => {conststore = createStore(... args)let dispatch = (a)= > {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`)}const middlewareAPI = {
getState: store.getState,
dispatch: (. args) = >dispatch(... args) }const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
...store,
dispatch
}
}
}
Copy the code
In fact, this can be understood as Russian nesting dolls. The original store.dispatch is the innermost one of the nesting dolls. All the middleware are nested one by one according to the order of array. The nesting process is done by Compose.
To understand this, let’s also take a look at the source code for the middleware Redux-Thunk. Redux-thunk makes our action function. Attention!!!!! The original action can only be a plain Object. The following is the way redux-Thunk middleware is written: store, Dispatch, action must be passed before the actual function entity is executed. We’re using a Corrification, and we don’t execute the function body until we have enough arguments.
// Slightly modified redux-thunk
const thunk = ({ dispatch, getState }) = > next => action= > {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk;
Copy the code
Why was dispatch assigned to an empty function in the first place? Compose returns a new dispatch that replaces the empty function
const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)Copy the code
Finally, the store variable is returned.
return {
...store,
dispatch
}
Copy the code
reference
- redux middleware
- Redux source