Introduction to applyMiddleware

ApplyMiddleware, as one of the core APIS of Redux, is essentially to do some operations before the reducer changes to dispatch. The specific implementation is actually to enhance the store. The final one is an enhancement to Dispatch in store. Use of applyMiddleware can be found in the Redux source code in the previous chapter (1).

Middleware lets you wrap a store’s dispatch method to do what you want. Middleware also has the key feature of “composability.” More than one middleware can be combined to form a chain. Each middleware doesn’t need to know anything about the middleware before or after it in the chain.

The source code structure for applyMiddleware is shown below.

Let’s take a look at how applyMiddleware can be used, in a current demo:

// middleware
const Logger = (store) = > (next) => (action) = > {
    console.info('logger start');
    let result = next(action);
    console.info('logger end');
};
const Test = (store) = > (next) => (action) = > {
    console.info('test start');
    let result = next(action);
    console.info('test end');
};

let store = createStore(rootReducer, applyMiddleware(Logger, Test));
Copy the code

There are two apis involved: createStore and applyMiddleware. In createStore source analysis, when enhancer is mentioned, execute the following statement directly.

return enhancer(createStore)(reducer, preloadedState);  
Copy the code

The value is known based on the current example. Enhancer is the result of applyMiddleware(Logger, Test).

2. ApplyMiddleware source code analysis

The source code for applyMiddleware is as follows:


import compose from './compose';

export default function applyMiddleware(. middlewares) {
    /* Let store = createStore(rootReducer, applyMiddleware(Logger, Test)); 2. In the createStore method, if enhancer exists, execute the statement enhancer(createStore)(Reducer, preloadedState); Enhancer is the result of applyMiddleware(Logger, Test). * /
    return createStore= >(... args) => {/* 1; /* 2; The createStore parameter is the createStore method of redux. Agrs represents reducer, preloadedState 2, store is the result of createStore without enhancer. This is the original store */ before middleware is used
        conststore = createStore(... args);// Dispatching is defined in a call to throw new Error (dispatching is often constructing middleware but not allowing other middleware applications).
        let dispatch = (a)= > {
            / /...
        };
        // debugger
        // Define middlewareAPI to pass getState and Dispatch to middleware. This is why state is accessible in the middleware.
        const middlewareAPI = {
            getState: store.getState,
           // Redefine dispatch. This is not a direct assignment, because dispatch is a reference assignment. If a direct assignment is made, subsequent changes to dispatch will affect the original dispatch
            dispatch: (. args) = >dispatch(... args) };/* 1, middleware form store => next => action =>{}, 2, return [(next)=>acticon=>{...next(action)...}] array, assign value to chain 3, use current demo as instance. And the result of Logger execution is F1, and the result of Test execution is F2 */
        const chain = middlewares.map(middleware= > middleware(middlewareAPI));
   
        / * 1, the compose (... Funcs) returns: (... args)=>(funcA(funcB(... FuncA and func are the elements of funcs. The compose (... Result of chain execution (... args)=>(f1(f2(... Args), where f1 and F2 are the results of Logger and Test execution defined above. The result of chain (store.dispatch) is F1 (f2(store.dispatch)), and F2 (store.dispatch) is used as the next parameter of F1. Dispatch = (action) => {... f2(store.dispatch) (action) ... 5, when executing store.dispatch(action), the actual execution is (action) => {... f2(store.dispatch) (action) ... }. 6. F2 will be executed. The next parameter is store.dispatch and the action parameter is Action. 7. If there are multiple middleware. Then, the enhanced dispatch is: F1 (f2(F3 (... Fn (store. Dispatch)))). Next = f2(f3(... Fn (store.dispatch)), next(Action) f2(f3(... Fn (store.dispatch)) (action) f2 (next = f3(... Fn (store.dispatch)), and so on until next is store.dispatch. 10. The next(action) statement must be executed in the middleware function. * /dispatch = compose(... chain)(store.dispatch)return {
            ...store,
            dispatch  // Return the enhanced store
        };
    };
}

Copy the code

ApplyMiddleware source code analysis, the comments section is detailed. ApplyMiddleware returns a higher-order function that acts on middleware.

const enhancer = createStore= >(... args) => { ....... }// statement (1)
Copy the code

Enhancer is passed as a parameter to createStore. When enhancer is present in the createStore method, the following statement is executed:

return enhancer(createStore)(reducer, preloadedState);  // statement (2)
Copy the code

At this time, the actual high-order function is the execution of statement (1), where the passed parameters are createStore (the createStore method of Redux), Reducer and preloadedState successively.

The applyMiddleware logic is all in the higher-order functions that are returned, and it does a few things:

2.1. Obtain the original Store

 conststore = createStore(... args);Copy the code

According to the above analysis, createStore is the createStore method of Redux… Args is reducer and preloadedState. The store created by the current reducer when there is no middleware. As mentioned in the previous section, middleware is essentially an enhancement to the Store, and getting the original store is just preparation for the later enhancement.

2.2. Middleware initialization

This is called middleware initialization, which is not very accurate. It can be understood as the execution of middleware, because middleware is a higher-order function. This is defined as the first execution of middleware, which can be compared with the source code structure diagram above. The corresponding code is as follows:

const middlewareAPI = {
    getState: store.getState,
    dispatch: (. args) = >dispatch(... args) };const chain = middlewares.map(middleware= > middleware(middlewareAPI));
Copy the code

The middlewareAPI contains the getState and dispatch properties. Middlewares is a list of middleware items that are passed in, and the map returns something like :(next)=>acticon=>{… next(action)… }. Using the current demo as an example, execute Logger and Test respectively and return results marked F1 and F2 (for convenience).

 let f1 = next= > action =>{
     // Logger
     next.action()
 }
 
 let f2 = next= > action =>{
     // Test
     next.action()
 }
 
 chain = [f1, f2]
Copy the code

2.3 Middleware combination

export default function compose(. funcs) {
  / /... Boundary judgment
    const a = funcs.reduce((a, b) = > {
        return (. args) = >a(b(... args)); });return a;
}
Copy the code

Take a look at the compose method source code. Loop through the reduce method of array, realize the combination of functions, and finally return a compound function.

compose(func1, func2, func3, ... Funcn) back to (... args) => f1(f2(f3(.... fn(args))))Copy the code

2.4. Enhance Dispatch and return to the latest store

dispatch = compose(... chain)(store.dispatch)Copy the code

This sentence is the key to the entire applyMiddleware code. According to 2.3, compose(… The result of chain execution is the combination of all middleware functions after the first execution (with the argument store).

F1, F2...... Fn is a higher-order function returned by middleware after the first execution :(next) =>acticon= >{... next(action)... } compose(... The result of chain (store.dispatch) can be interpreted as f1(f2(f3(....) Fn (store.dispatch)), next = f2(f3(....) Fn (store.dispatch)), and assign the value dispatch =action= > {
    / /... beforef2(f3(.... fn(store.dispatch))(action);/ /... after
}


Copy the code

The combination takes store.dispatch as an entry and returns a new dispatch function that overwrites the original Dispatch property of store. This preserves consistency with the createStore output. An enhancement to Dispatch is also implemented, which explains that applyMiddleware is essentially an enhancement to Dispatch.

When store.dispatch(action) is executed, the current dispatch is the enhanced dispatch. Continue analysis

According to the above analysis, the current dispatch is: Dispatch =action= > {
    / /... Before // (before of 1st middleware)f2(f3(.... fn(store.dispatch)(action);/ /... After // (after for 1st middleware)} after executing store.dispatch(action), (1) first execute :(no1Before), (2) then run f2(f3(.... Fn (store.dispatch))(action), next = f3 for f2 (....) Fn (store. Dispatch). f2 =next= > action =>{
    / /... Before // (before of 2nd middleware)
     next(action)
    / /... After // (after for 2nd middleware)} (3) Execution: (No2Before of middleware) (4) then execute next(Action), which is f3(.... fn(store.dispatch)(action) (5F1 ---- next = f2(f3(... fn(store.dispatch))) f2 ---- next = f3(... fn(store.dispatch)) f3 ---- next = f4(... fn(store.dispatch)) fn ---- next = store.dispatch ...... (6) execution :(no2Middleware after) (7) execution :(no1Middleware after)Copy the code

The analysis of the above part is also consistent with the introduction of middleware in Redux official documents. Executing next (action) in each middleware is key to ensuring that the middleware forms a chain of middleware.

. Middlewares (arguments): Functions that follow the Redux Middleware API. Each Middleware takes Store dispatch and getState functions as named arguments and returns a function. This function is passed into the next Middleware dispatch method called Next and returns a new function that receives an action, which can call Next (Action) directly, at any other time needed, or not call it at all. The last middleware in the call chain accepts the actual Store’s dispatch method as the next argument and ends the call chain. So, the middleware function signature is ({getState, dispatch}) => next => action

2.5. Onion model

According to the above analysis, the middleware does some actions before the reducer change of dispatch. According to 2.4, the actual execution of the enhanced dispatch is as follows:

The process corresponds to the onion model, working from the outside to the inside and then working from the inside to the outside.

Combined with the examples in this paper. The output result of middleware dispatch is as follows:

logger start
test start
test end
logger end
Copy the code

3, summary

  • Middleware is essentially a store enhancement, specifically a dispatch enhancement in the store;
  • Middleware can form a chain of combinations, depending on the layer of next(action) transmission;
  • Middleware calls follow the Onion model, from outside to inside and out again.

Redux source code analysis (1) – Redux introduction and use

Redux source code Analysis (2) – createStore

Redux (3) – applyMiddleware

Redux source Code Analysis (4) – Combiner Creators and bindActionCreators