Tutorial source code and directory
Https://github.com/lewis617/myReact
Today, we are going to talk about the knowledge point of customizing the Redux middleware. This section is very abstract, especially the definition principle of middleware, the multi-layered function nesting and concatenation, need very strong logical thinking ability to completely digest absorption. But I'll say a few more words, so don't worry.
Example
An example is the official example Real-world, which is a program that gets GitHub users and repositories.
This example source code:
Https://github.com/lewis617/myReact/tree/master/redux-examples/real-world
Redux Middleware is a nested function
Redux in the middle of a total of three layers of functions, respectively, the store, next, action, the three parameters. The return is next.
Why do you want to nest functions? Why not pass three parameters in a layer of functions, and pass a parameter in a layer of functions, and pass a total of three layers? Because the middleware is for multiple end-to-end connections, the next layer of "processing", so next must be a separate layer. What about store and action? Store, we're going to put the store on top of the middleware because we're going to use the store's dispatch and GetState two methods. Action, because we encapsulate so many layers, in fact, in order to make a more advanced dispatch method, but in the advanced is dispatch, is dispatch, you have to accept the action parameter.
Look at the code in our example:
Middleware/api.js
// A Redux middleware that interprets actions with CALL_API info specified.
// Performs the call and promises when such actions are dispatched.
export default store => next => action => {
const callAPI = action[CALL_API]
if (typeof callAPI === ‘undefined‘) {
return next(action)
}
let { endpoint } = callAPI
const { schema, types } = callAPI
if (typeof endpoint === ‘function‘) {
endpoint = endpoint(store.getState())
}
if (typeof endpoint !== ‘string‘) {
throw new Error(‘Specify a string endpoint URL.‘)
}
if (!schema) {
throw new Error(‘Specify one of the exported Schemas.‘)
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error(‘Expected an array of three action types.‘)
}
if (!types.every(type => typeof type === ‘string‘)) {
throw new Error(‘Expected action types to be strings.‘)
}
function actionWith(data) {
const finalAction = Object.assign({}, action, data)
delete finalAction[CALL_API]
return finalAction
}
const [ requestType, successType, failureType ] = types
next(actionWith({ type: requestType }))
return callApi(endpoint, schema).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || ‘Something bad happened‘
}))
)
}
In the example, we implemented a three-layer function nesting using the store and next = action. The arrow syntax is a good substitute for the ugly function nesting method. The last point of the {}, we can write about the dispatch decoration, but remember to return to next, for the next middleware use.
Implementation of middleware
A nested function is also a function, which is the function to run. We know that JS inside, we use () to execute a function. So what are we going to do about the three-layer nesting function? Write three () Bai! AAA () () () () is available. AAA () returns a function, AAA () () returns a function, and AAA () () () finally executes.
Let's take a look at how the middleware we're writing works. First, the use of middleware in the configstore inside the Applymiddleware inside the written.
Applymiddleware (thunk, API)
Let's look at the source code of redux:
Node_modules/redux/src/applymiddleware.js
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, initialState, enhancer) => {
var store = createStore(reducer, initialState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
Good short good happy! But it looks pretty complicated, don't worry, it's just a three-layer nesting function, let's look at the outermost execution code first:
Chain = middlewares.map (middleware = middleware (MIDDLEWAREAPI))
Middlewares uses the expand syntax to generate an array of middleware from the middleware we write in. Iterate through each middleware, executing the first time in the outermost layer of the middleware, that is, the parameter is store. We found the store to be middlewareapi.
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
is an object that contains the two methods we need, which is enough.
Well, let's start looking for the execution code for the second-level function:
dispatch = Compose (... chain) (Store.dispatch)
What the hell is this? Compose is a kind of function nesting, the source code list does not show, we know that it can help us to put nested functions, as a comma-separated look is possible. Somewhat similar to the promise solution to callback the infernal practice. is to write the nesting in parallel.
Chain is our second-level function, ... is to expand the syntax, separated by commas into the compose parameter. The back one (Store.dispatch) is the entry parameter. is a dispatch without any processing, this poor fellow in, will be our middleware layer processing, experience wind and rain, become more dispatch.
Where is the execution code for the third-level function? Not here, because the third layer of the function, is the new dispatch, we want to use it within the component, so the third layer of function execution in the component. What are the parameters? It's a lovely action, of course!
The connection of the middleware
We know that middleware can be used end-to-end, then how do we achieve the end-to-end connection? The answer is in next (), the students who wrote the Express middleware must be familiar with it. So what is next () Redux middleware? is also a dispatch.
When we write middleware, we must return to next (), this next is the current middleware to dispatch processing, after processing back to the back of the middleware to continue processing. This is why the order of the middleware has a particular reason.
Interpreting the business process of the middleware in the example
The principle of middleware is finished, let's go through the middleware business process in the example!
Gets the specified action
When we define the action, we return the things we need in the functions such as Fetchuser :
Actions/index.js
import { CALL_API, Schemas } from ‘../middleware/api‘
export const USER_REQUEST = ‘USER_REQUEST‘
export const USER_SUCCESS = ‘USER_SUCCESS‘
export const USER_FAILURE = ‘USER_FAILURE‘
// Fetches a single user from Github API.
// Relies on the custom API middleware defined in ../middleware/api.js.
function fetchUser(login) {
return {
[CALL_API]: {
types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ],
endpoint: `users/${login}`,
schema: Schemas.USER
}
}
}
// Fetches a single user from Github API unless it is cached.
// Relies on Redux Thunk middleware.
export function loadUser(login, requiredFields = []) {
return (dispatch, getState) => {
const user = getState().entities.users[login]
if (user && requiredFields.every(key => user.hasOwnProperty(key))) {
return null
}
return dispatch(fetchUser(login))
}
}
We specify these actions to be "packaged". In addition to Fetchuser, there are others that do not list the code.
We add console.log to see what the action really looks like:
Console.log (' Currently executing action: ', action); = Action[call_api]
When we execute the action of initroutes, we get an action object that contains the symbol (), which is the action we want.
Symbol ()
What is Symbol ()? Is Call_api.
Middleware/api.js
// Action Key that carries API call info interpreted by this Redux middleware. Export Const CALL_API = Symbol (' Call API ')
Why use the symbol, in order to not conflict, symbol is a unique invariant identifier that can be used for the object's key. In order to avoid conflict, you can also use a string to replace, but not good, because the string is easy to name the conflict, you have to think of a wonderful long name, so still use the symbol bar. Again, when we write Object.assign, the first parameter is set to {}, the second parameter is set to the source, and the third parameter is set as the extension attribute, which is to be compatible with the object containing the symbol.
Disinfection
After getting to the specified action, we will do a series of exception handling:
Middleware/api.js
if (typeof endpoint === ‘function‘) {
endpoint = endpoint(store.getState())
}
if (typeof endpoint !== ‘string‘) {
throw new Error(‘Specify a string endpoint URL.‘)
}
if (!schema) {
throw new Error(‘Specify one of the exported Schemas.‘)
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error(‘Expected an array of three action types.‘)
}
if (!types.every(type => typeof type === ‘string‘)) {
throw new Error(‘Expected action types to be strings.‘)
}
Relatively simple, not wordy.
Execute Request action
After getting the action, it has also been disinfected, you can start the AJAX request, first send a request action
function actionWith(data) {
const finalAction = Object.assign({}, action, data)
delete finalAction[CALL_API]
return finalAction
}
const [ requestType, successType, failureType ] = types
next(actionWith({ type: requestType }))
Performs an AJAX request, sends a successful or failed action after the end
return callApi(endpoint, schema).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || ‘Something bad happened‘
}))
)
Schematic flow
Originally an action, after the processing of the middleware, became a series of processes.
React+redux Tutorial (vii) Customizing Redux middleware