preface
Recently, out of curiosity about Redux and wanting to get to the bottom of what I’ve used, I did a Redux self-test to get to the bottom of its essence. Below, I will make a list of questions that arise after using Redux, and use the problem guide to dig deeper and experience the clever design of Redux.
Issues list
- How does state initialization make it globally accessible?
- What did Redux do after Dispatch?
- How was reducer handled?
- After the data in state is modified, how do subscribers receive the updated data?
- How does React sense state changes after sending dispatch?
- Why is a temporary variable dispatch assigned twice in applyMiddleware?
- Why is the Dispatch of the middlewareAPI in applyMiddleware wrapped in anonymous functions?
Redux source directory introduction
Five interfaces exposed by Redux:
- CreateStore (creates a Store and its corresponding Dispatch and subscribe operations)
- CombineReducers (Merge multiple reducers into a total Reducer)
- BindActionCreators (the function that returns the package dispatch can be used directly. Commonly used in mapDispatchToProps
- ApplyMiddleware (provides middleware such as redux-thunk, redux-Logger)
- Compose (tool function to be used in combiner ers)
We will use the above interface to answer our questions one by one. Ps: the following source code is simplified after
What did Redux do after Dispatch? After the data in state is modified, how does the page receive the updated data?Copy the code
These answers are in createstore.js, so let’s take a look at the code structure:
createStore(reducer, preloadedState, If (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = PreloadedState preloadedState = undefined} let currentReducer = reducer // The reducer can be dynamically replaced by the reducer in store.replaceReducer mode, which provides the possibility of hot code replacement. CurrentListeners = preloadedState // currentState let currentListeners = [ Note How often are we dispatching dispatching agents? How often are we dispatching dispatching agents? Function getState() {// Return the current state, you can call store.getState() to get the data in store... Function subscribe(listeners) {function subscribe(listeners) {function subscribe(listeners) {function subscribe(listeners) {function subscribe(listeners) { To remove the listener from the array. // Note, however, that both operations take place when dispatch is not executing. Note that the Listeners array cannot be changed because the update function is looped during dispatch execution... Function dispatch(action) {// Call the listeners on the reducer according to the action and the current state, return a new state loop call to the listeners array, execute the update function GetState () gets state from store. GetState (), which is the latest state. } return { dispatch, subscribe, getState, } }Copy the code
We know that the page can retrieve the current state via store.getState(), and that the page sends a dispatch(type) if the data is changed, which is the key to the problem:
function dispatch(action) { if (! isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant? ')} // If you are dispatching, you cannot dispatch again. If (isDispatching) {throw new Error('Reducers may not dispatch actions.')} try { CurrentState = currentReducer(currentState, Action) finally {isDispatching = false} // The listeners in subscribe() are run in turn, and the array is filled with the functions that need to fetch data. // Linstener for (let I = 0; i < listeners.length; I ++) {const listener = listeners[I] // The reason the latest status is not recorded here is because we call the store.getState() method directly to get the latest status. This is also how the page is notified to update the data after it has been changed. listener() } return action }Copy the code
Answer the above questions:
Q: How did Redux handle it after Dispatch? After the data in state is modified, how does the page receive the updated data?
A: First, new state will be obtained by using the current reducer, state and the incoming parameter action. Then, by triggering the function in the listening array, the store.getState() used in the function will be triggered again to inform data update.
Combinereducers.js combinereducers.js
How was reducer handled?Copy the code
< reducer > < reducer > Form an array of const reducerKeys = object.keys (reducers) // The final set of const finalReducers = {} // Go through all reducers FinalReducers becomes a reducer object with key(Reducer name) : value(Reducer execution function). for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } Const finalReducerKeys = object.keys (finalReducers) // This function is the core of combinereducers.js, and the main idea is to compare the incoming reducer, If any state returned by the Reducer is different from the previous state, a new state is returned. (In fact, there is a question here, if this function only changes a reducer state data, it will return a new state, why is it processed like this?) 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] // Get the old state const from each reducer PreviousStateForKey = state[key] // Derive new state from old state (advantage of pure functions: the same state and the new state returned by the action will not change.) const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey ==='undefined') { const errorMessage = getUndefinedStateErrorMessage(key, Action) throw newError(errorMessage)} // The last step is to check whether the state is the same as the previous state. nextState[key]= nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey } return hasChanged ? nextState : state }Copy the code
To convert a function that takes multiple arguments into a set of nested functions, you need to know the basics of node middleware before reading applyMiddleware. It returns a new function that expects to pass in the next argument. Read Applymiddleware.js to answer these questions:
Why is a temporary variable dispatch assigned twice? Why is middlewareAPI's Dispatch wrapped in anonymous functions?Copy the code
Let’s analyze the source code one by one:
function applyMiddleware(... middlewares) { return (createStore) => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState) let dispatch = () => { 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: (action, ... args) => dispatch(action, ... args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch) return { ... store, dispatch } } }Copy the code
You can see the overall construction of the incoming middleware (middlewares) and the internal functions returning the normal store and the transformed Dispatch. The converted dispatch is composed by compose(… Chain)(store.dispatch) Chain comes from the fifth exposed interface, comemage. js, which is essentially what we call an kerochemical handler, and chain is an array like the following:
next => action => {
return next(action)
}
Copy the code
compose(… Chain simply concatenates the functions in the array and does not execute. Compose still returns a layered function. As follows:
const composedFunc = (next3) => {
return
((next2) => {
return func1(func2(next2))
})(func3(next3))
}
Copy the code
The actual execution is in the compose(… Chain)(dispatch), this dispatch is the next3 above, next2 is the return value of func3(Next3), and so on, as follows:
func1(func2(func3(dispatch)));
Copy the code
console.log("coreFun1 run");
((action) => {
console.log("coreFunc2 run");
((action) => {
console.log("coreFunc3 run");
dispatch(action);
})(action);
})(action);
}
Copy the code
According to the Node middleware Onion model, all the middleware will process the action and pass it in, and finally trigger dispatch in the innermost layer and pass the result out as a parameter, and finally get a new function.
And look up at the source code,
const store = createStore(reducer, preloadedState) let dispatch = () => { 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: (action, ... args) => dispatch(action, ... args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(... Chain)(store.dispatch) // Second assignmentCopy the code
This section is also intended to give each middleware a basic getState and dispatch. To solve some oft-asked questions:
- Why is a temporary variable dispatch assigned twice? The first thing you can tell from the throw Error returned by the first variable is that the code expects dispatch not to be called when the Middleware array is being built, or it will throw. It’s called once in the middlewareAPI dispatch but it doesn’t raise this throw Error, Because dispatch is not actually called until the second assignment to dispatch (as we read earlier, the call is not triggered until the compose function is passed in (store.dispatch), So the middlewareAPI’s dispatch does not fire at this point.
- Why is middlewareAPI’s Dispatch wrapped in anonymous functions? The idea is that if each middleware component makes a change to its dispatch, the middleware component will make a change to its dispatch. Chain)(store.dispatch) triggers the middlewareAPI’s dispatch call.
This solves the problems associated with applyMiddleware and completes redux’s analysis. (Due to bindActionCreators’ limitations, we will not go into details here.) , if there is a question or interpretation of the wrong place, but also hope that the leaders correct.
Reference links:
Read the source code to understand Redux Middleware – FreewheelLee’s article
Principles of Redux Middleware – From Blindsided to Enlightened – article by Dell Lee
A brief review of Redux source code and operation mechanism – Nero article