Redux’s Middleware allows you to send asynchronous actions (web requests) and print logs of those actions. Relatively speaking, Redux’s store enhancer concept is not well understood by many people.

1. Basic concepts and usage

Redux creates a store through the createStore API. The createStore function is signed as follows:

createStore(reducer, [preloadedState], [enhancer])
Copy the code

Where the third optional parameter, enhancer, is the store enhancer.

Store enhancer, can be translated as store enhancer, as the name implies, is to enhance the functionality of the store. A store enhancer is a higher-order function that takes store Creator as an argument and returns a function that creates a more powerful store. This is similar to the higher-order component concept in React.

The general structure of the Store enhancer function is as follows:

function enhancerCreator() {
  returncreateStore => (... args) => { //do something based old store
    // return a new enhanced store
  }
}
Copy the code

Note that enhancerCreator is the function used to create a Store Enhancer, meaning that the result of enhancerCreator is a Store Enhancer. (… The args parameter represents the parameters required to create a store, which the createStore receives, and is actually (Reducer, [preloadedState], [enhancer]).

Now we create a store enhancer that prints the information for the sent action and the state change:

// autoLogger.js
// store enhancer
export default function autoLogger() {
  return createStore => (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    function dispatch(action) {
      console.log(`dispatch an action: ${JSON.stringify(action)}`);
      const res = store.dispatch(action);
      const newState = store.getState();
      console.log(`current state: ${JSON.stringify(newState)}`);
      return res;
    }
    return{... store, dispatch} } }Copy the code

AutoLogger () has changed the default behavior of Store Dispatch to output logs before and after each action is sent. AutoLogger () = store enhancer;

// configureStore.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from 'path/to/reducers';
import autLogger from 'path/to/autoLogger'; Const store = createStore(Reducer, autoLogger());Copy the code

2. Its relationship with middleware

If you know about Redux-Logger, autoLogger() does something similar to it. Could Store Enhancer and Middleware do the same thing? That’s true. The middleware implementation function, applyMiddleware, is itself a store enhancer.

export default functionapplyMiddleware(... middlewares) {returncreateStore => (... Args) => {// omitreturn {
      ...store,
      dispatch
    }
  }
}
Copy the code

The structure of applyMiddleware is identical to the store Enhancer mentioned earlier, applyMiddleware(… Middlewares) results in a store enhancer. So, anything you can do with Middleware can also be done with Store Enhancer. From applyMiddleware (… Middlewares) final return structure {… Store, dispatch} applyMiddleware(… Middlewares) this store enhancer is primarily used to modify the store dispatch method, which is exactly what middleware does: enhance store dispatch. Middleware is actually applyMiddleware(… Middlewares) a layer of abstraction above the store enhancer, applyMiddleware(… Middlewares) is passed to each of the Middleware arguments {getState, dispatch}, and middleware strengthens the dispatch method.

When applyMiddleware is used (… Middlewares, middlewares, and other store enhancers can be grouped into one using redux’s compose function:

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from 'path/to/reducers';
import autLogger from 'path/to/autoLogger'; const enhancer = compose( applyMiddleware(... middlewares), autLogger() ); Const store = reducer (reducer, enhancer);Copy the code

The first enhancer in compose is in the outermost layer of the onion model, and the last enhancer is in the innermost layer of the onion model. Will be processed by each enhancer layer of the Onion model from outside to inside, as shown in the figure below. Since we normally handle asynchronous actions through middleware, to ensure that other enhancers receive normal actions, we need to make applyMiddleware(… Middlewares) as the first store enhancer for compose, lets all actions pass through applyMiddleware(… Middlewares).

ApplyMiddleware (… Middlewares (Middlewares) limits middleware to only enhancing store dispatches, but that’s its own specification. For other store enhancers, you can enhance any method contained in a store. Such as Dispatch, SUBSCRIBE, getState, replaceReducer and so on. After all, a Store is just a normal JavaScript object that contains functions, and you can easily copy the methods and add new functionality.

Let’s look at another example, redux-localstorage. This store enhancer is used to automatically persist the state in the store to localstorage. Its main code is as follows:

// store enhancer
export default functionPersistState (Paths, config) {// Some feature options are configuredreturn next => (reducer, initialState, enhancer) => {
    let persistedState
    let finalInitialState

    try {
      persistedState = deserialize(localStorage.getItem(key))
      finalInitialState = merge(initialState, persistedState)
    } catch (e) {
      console.warn('Failed to retrieve initialize state from localStorage:', e)} const store = next(reducer, finalInitialState, enhancer) const slicerFn = slicer(paths)function () {
      const state = store.getState()
      const subset = slicerFn(state)

      try {
        localStorage.setItem(key, serialize(subset))
      } catch (e) {
        console.warn('Unable to persist state to localStorage:', e)
      }
    })

    return store
  }
}
Copy the code

What this enhancer does is simply subscribe to store changes immediately after the store is created and persist state to localStorage when state changes in the store.

3. Enhancer

In store Enhancer, we can copy and change methods in store at will, so when we customize store enhancer, we may break the normal workflow of Redux and the whole application will not work properly. Here is an example of a destructive enhancer:

export default function breakingEnhancer() {
  return createStore => (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    function dispatch(action) {
      console.log(`dispatch an action: ${JSON.stringify(action)}`);
      console.log(`current state: ${JSON.stringify(newState)}`);
      return res;
    }
    return{... store, dispatch} } }Copy the code

This example recreates a Dispatch method, but in the new Dispatch method, instead of calling the old Dispatch method to send the action, the action cannot be sent properly and the entire application will not work. So, when customizing store Enhancer, be careful not to break Redux’s original workflow.


Welcome to pay attention to my public number: the big front of old cadres, receive 21 big front selection books!