Redux source code analysis -- Enhancer

The translation of store enhancer into Chinese is the store enhancer. The essence of store middleware is also the implementation of enhancer, and the enhanced dispatch function of store middleware. The implementation of store enhancer is not only to enrich the dispatch function, but also to add new methods through the store object created by createStore(reducer, preloadedState, enhancer)

In Redux source code, there are very few lines of code involving enhancer. The previous judgments are all about the parameters passed by createStore: 1. If the second parameter preloadedState is a function and the third parameter is not passed, then enhancer = preloadedState, preloadedState = undefined. 2. Enhancer cannot have more than one and one. If all of them are satisfied, enhancer exists again. Execute enhancer(createStore)(reducer, preloadedState), the internal content of enhancer function, and implement it by yourself

Through enhancer(createStore)(reducer, preloadedState), the implementation model of enhancer is determined as follows:

    (createStore) => (reducer, preloaderState) => {}

If you want to do multiple enhancer s, you need to use the compose function we introduced earlier. The compose function is to combine multiple functions into one function, which meets the requirements of createStore. If you want to know more about compose, go to Redux source code analysis -- compose

if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

Here is an example:

For example, you want to use enhancer to implement a log before and after state change:

let enhanncer = (createStore) => (reducer, preloadedState) => {
	let store = createStore(reducer, preloadedState);

	let dispatch = (action) => {
		//Value of state before dispatch(action)
		console.log('before: ', store.getState());

		store.dispatch(action);

		//Value of state after dispatch(action)
		console.log('after: ', store.getState());

		return action;

	}

	return {
		...store,
		dispatch
	}
}

let store = createStore(reducer, enhancer);

If there are multiple enhancer s, how to implement them?

Use compose to combine it into a function, as shown in the following example:

let e1 = (createStore) => (reducer, preloadedState) => {
	console.log('e1: ', createStore);
	let store = createStore(reducer, preloadedState);
	console.log('e1-after');

	return {
		...store
	}
}

let e2 = (createStore) => (reducer, preloadedState) => {
	console.log('e2: ', createStore);
	let store = createStore(reducer, preloadedState);
	console.log('e2-after');

	return {
		...store
	}
}

let e3 = (createStore) => (reducer, preloadedState) => {
	console.log('e3: ', createStore);
	let store = createStore(reducer, preloadedState);
	console.log('e3-after');

	return {
		...store
	}
}
  
let enhancer = compose(e1, e2, e3);

let store = createStore(reducer, enhancer);

Operation result:

 

From the above results, we can see that: when multiple enhancers are used, use compose to merge them into one enhancer. Before and after a series of enhancers are run, use compose to run from left to right in turn. When an enhancer is executed, if you execute createStore (reducer) in (createStore) = > {}, When preloadedstate), the execution goes to the next enhancer, because the current createStore is not the real createStore(), but the next enhancer's (reducer, preloadedstate) = > {}. When executing to the rightmost enhancer, execute in reverse order to the leftmost, as shown in the result,

e1 e2 e3 e3-after e2-after e1-after

How to convert enhancer and middleware?

In essence, middleware is an enhancer, but middleware is only enriching dispatch. How can we convert middleware to enhancer?

compose(enhancer1, enhancer2, applyMiddleware(middleware1, middleware2))

When asynchronous and synchronous middleware are running at the same time, how to store the location?

The middleware of asynchronous function is stored on the far right of applyMiddleware, because if it is placed in another location, it may not work for the middleware on the right.

When both middleware and enhancer exist, what is the order of code execution?

You can understand as follows: because enhancer is to enhance the function of store, it must run before dispatch(action), that is, the initial stage of code, while middleware needs action to execute (action) = > {}, so the code in middleware is later than enhancer.

The source code analysis is as follows:

1. According to let store = applyMiddleware(m1, m2, m3)(createStore)(reducer, preloadedState, compose(e1, e2, e3)), the code should execute applyMiddleware() method first, because the first line of applyMiddleware method executes createStore(...args), then execute compose(e1, e2, e3)(createStore)(reducer, preloadedState), that is, to process enhancer first, and then finish processing. In the middle ware processing (its processing requires dispatch(action)), the following is illustrated by an example:

let e1 = (createStore) => (reducer, preloadedState) => {
	console.log('e1');
	let store = createStore(reducer, preloadedState);
	console.log('e1-after');

	return {
		...store
	}
}

let e2 = (createStore) => (reducer, preloadedState) => {
	console.log('e2');
	let store = createStore(reducer, preloadedState);
	console.log('e2-after');

	return {
		...store
	}
}

let e3 = (createStore) => (reducer, preloadedState) => {
	console.log('e3');
	let store = createStore(reducer, preloadedState);
	console.log('e3-after');

	return {
		...store
	}
}

let m1 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m1');
	next(action);
	console.log('m1-after');
}

let m2 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m2');
	next(action);
	console.log('m2-after');
}

let m3 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m3');
	next(action);
	console.log('m3-after');
}
  
let enhancer = compose(e1, e2, e3);

let store = applyMiddleware(m1, m2, m3)(createStore)(reducer, enhancer);

Execution result: E1 E2 E3 E3 after E2 after E1 after when the action is triggered, print out: M1 M2 m3 after M2 after M1 after

2. When let store = createStore(reducer, preloadedState, compose(e1, e2, e3, applyMiddleware(m1, m2, m3))), the execution result is the same.

If there is another dispatch(action) in middleware, what is the execution order of Middleware

This is for asynchronous situations, such as asynchronous ajax requests. Let's simulate this situation, but you can't use dispatch(action) directly in middleware. If you use it directly, there may be a life and death cycle. Therefore, when using dispatch(action) in middleware, there must be a condition judgment. Only when this condition is met can dispatch(action) be carried out. If it is unlimited, it will be a dead cycle.

An example of advanced line:

let m1 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m1-next-before');
	next(action);
	console.log('m1-next-after');
}

let m2 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m2-next-before');
	//When the dispatch(action) is executed (ensure that isNotGo is not true), when it is executed here, the dispatch({type: 'CHESHI', isNotGo: true}) will be executed again
	//If it is not set in this way, it may cause a dead cycle
	if(!action.isNotGo) {
		dispatch({type: 'CHESHI', isNotGo: true});
	}	
	next(action);
	console.log('m2-next-after');
}

let m3 = ({getState, dispatch}) => (next) => (action) => {
	console.log('m3-next-before');
	next(action);
	console.log('m3-next-after');
}

let store = applyMiddleware(m1, m2, m3)(createStore)(reducer);

After the dispatch(action) is triggered, the result is:

It can be seen from the example that when the dispatch(action) is triggered, M1 next before is executed. When the middleware m2 is run, m2 next before is output. At this time
Judge that! action.isNotGo is true, and dispatch(action) will be carried out. The program will start from m1 again and execute in turn. When it reaches m2 again,! action.isNotGo
If it is false, no more dispatch(action) will be executed to m3, that is to say, the result should be M1 next before M2 next before M1 next before
m2-next-before m3-next-before m3-next-after m2-next-after m1-next-after, next(action) will continue from m2,
Output: m3-next-before m3-next-after m2-next-after m1-next-after

Published 17 original articles, won praise 0, visited 1658
Private letter follow

Posted on Tue, 17 Mar 2020 06:01:26 -0400 by theCro