preface

📢 blog launch: Hiro’s blog

Before this article begins, nagging a few words, that is, this article is a bit long, and some source code, etc.; A few days ago, I was lucky to have a chat with Elder brother Han Yan. He said that NOW I already know how to write articles. He suggested that the next stage should be stable, and then write something profound, rather than floating on the surface. Last Saturday, I went to listen to the technical writing sharing of Mr. Zhang, who has published a book with the company.

Therefore, I kept silent for a while and listened to the advice of some seniors. I decided to make a technical in-depth article ~, try to make one article per month, but each time, the length will be longer, and try to share a more complete topic. So a flu shot, I hope you can calm down to see, we progress together ~

❓ why this column is called [KT], I this person is relatively low, column Chinese called: wide Technology deep text, K from the width of the wide, T, Technology, Technology, forced case. Yeah, I feel like I’m taking my bragging skills one step further.

Article pipeline

Due to time constraints and the debate on react state management, a wave of communication was carried out around HOX, MOBx and Redux. Therefore, THE fourth step of hands-on practice will be discussed later. In the following period, I plan to study an internal implementation principle of HOX and MOBx. Then practice writing demo, review a wave in the group, take the essence and discard the dregs, maybe another new product? It’s very exciting and interesting to think about it

Don’t spray. They only made wheels to learn

This article is suitable for personnel

  • 🍉 Melon eating masses
  • Redux entry level player
  • Want to get the lowdown on Redux
  • Want to know the art of redux programming

What can you learn from reading this article

  • What is involved under popular scienceFunctional programming,The onion modelThe relevant knowledge
  • Sexy Peng, hand in hand take you to see redux source code
  • Learn about some of the design concepts in the Redux library
  • No longer afraid to be asked why Redux returns a new state
  • (I promise it will be hands-on!!)

The text start

background

When I had an interview at the end of 2018, the interviewer looked at my resume and asked, “I’ve used vue and React. Can you tell me the difference between vue and React?” “At that time, Lai Lai was forced to say something, but I didn’t know whether he was right. Then, when talking about Vuex and Redux, a murder happened. The interviewer asked why Redux always returned a new state. Why not go back to the old state? The interview results needless to say, after all, I did not know so much ~

After the interview, I took the time to read the source code of REdux again, OJBK, really look more dizzy, remember when I looked at the time, REdux has not introduced TS, some time ago, want to know about Redux in depth, who knows, out of control, ghost knows how many sentences I said in the process of looking at WC, awesome…

Although this article is written for redux beginners, due to my damn ritual sense, I have to say a few words before I say anything

Story is what?

Redux is a JavaScript state container that provides predictable state management solutions, as described on the website:

✋ Redux is a predictable state container for JavaScript apps.

Meh? Don’t understand? Wait a minute, before I explain, let me ask you a question. React is a one-way data flow or a two-way data flow? If you answered two-way data flow, ok, bye 👋, go out and turn left, if you answered one-way data flow, well, we are still good brothers ~

To understand what redux is, take a look at the diagram I drew 👇

React supports props and state. When we want to pass data from a parent component to a child component, we can use props to pass data. If we want to manage state within a component, we can use state. However, we ignored our own feelings about react

React is a one-way data stream. It doesn’t have the ability to track data up. You either distribute it down or manage it internally. (What about challenging authority? Do you think it’s okay?

Peng listened and said, “Oh no, can’t you change the state of the parent component by calling back?” Yes, it can. Let’s talk a little bit about why we use Redux. In general, any project that we use Redux in is a complete application. What if you want two sibling components to communicate, gossip, and exchange data?

Let’s simulate a scenario where the Peng and Kuan components want to share and exchange some data with each other.

The only solution is to raise the state of the Peng and Kuan components to the shared parent component, and then pass the data down from the parent component. The child component does the processing, and then the callback function passes back the modified state, which is partly reactive.

What’s the problem with that? You’ll find that if you want to share data, you have to put all the states that need to be shared on top of all the components, and then distribute them to all the components.

To do this, we needed a library to serve as a more elegant, professional top-level state for each component, so we introduced Redux, which is simply redux.

A Provider is a react-Redux library. It is a react-redux library.

💥 Again, redux has nothing to do with React!!

That’s why, in the React project, we always see this in the root App component.

function App() {
  return (
    <Provider store={store}>.</Provider>
  );
}
Copy the code

The three principles

Hiro here by default everyone will use Redux, you can not use the document, write a demo you will know, but it is still to say about the three principles of Redux ~

  • Single data source: for the entire applicationstateAre stored in a state tree and only exist inThe onlyIn a store
  • State is read-only: the only way to change state is by firingaction, and then dispatches through the type of the action. You cannot change the state of the application directly
  • Status changes are made byPure functionsTo describe how an action changes the state tree, writereducers

Basic knowledge reserve

Store

The store is generated by the createStore(Reducers, preloadedState, enhancer) method provided by Redux. As you can see from the function signature, you must pass in the reducers to generate the Store, and you can also pass in the initialization state (preloadedState) as a second optional parameter.

The third parameter is usually applyMiddleware(thunkMiddleware)

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk' // redux-thunk is used here

const store = createStore(
  reducerList,
  (initialState = {}),
  applyMiddleware(thunkMiddleware)
)
Copy the code

The core API in Redux is createStore. The store created by the createStore method is an object that contains four methods:

  • GetState () : Gets the current state of the store.
  • Subscribe (listener) : Registers a listener that is called when the store changes.
  • Dispatch (Action) : Dispatch an action and return the action. This is the only way to change the data in the store.
  • ReplaceReducer (nextReducer) : Updates the reducer in the current store. This reducer is usually called only in the development mode.

Aciton

Action is the payload that transfers data from the app to the Store. It is the only source of store data. Simply put, an Action is a message type that tells Redux what it is time to do and sends the data to Redux internally.

Action is a simple object. The Reducer must have a Type attribute, which indicates the Action type (determine the logic to be executed). Other attributes can be customized by the reducer. Such as:

const KUAN_NEED_GRID_FRIEND = 'KUAN_NEED_GRID_FRIEND'
Copy the code
// An action object
// Action tells Redux that Hiro wants a girlfriend
{
  type: KUAN_NEED_GRID_FRIEND,
  params: {
    job: 'Programmer'.username: O 'wide'}}Copy the code

Action Creator in Redux simply returns an Action

function fetchWishGridFriend(params, callback) {
  return {
    type: KUAN_NEED_GRID_FRIEND,
    params,
    callback,
  }
}
Copy the code

As we know, Redux is derived from Flux, in which Action Creators often trigger a Dispatch when called. For example, 👇

/ / traditional Flux
function fetchFluxAction(params, callback) {
  const action = {
    type: KUAN_NEED_GRID_FRIEND,
    params,
    callback,
  }
  dispatch(action)
}
Copy the code

In Redux, however, the Dispatch method exists in the Store, so we only needed to send the results of the Action Creators report to Dispatch () to complete the launch of a Dispatch. Even a bundled Action Creators was created to dispatch automatically

/ / general dispatch
store.dispatch(fetchWishGridFriend(params, () => {}))

/ / bind dispatch
const bindActionCreatorsDemo = (params, callback) = > (store.dispatch) =>
  store.dispatch(fetchWishGridFriend(params, callback))
bindActionCreatorsDemo() // You can implement a Dispatch action
Copy the code

👉 Is sure to find bindActionCreators() in your code, because normally we use the React-Redux connect helper. BindActionCreators () can automatically bind multiple action creator functions to the Dispatch () method.

Reducers

Reducers must be a pure function that handles state updates based on action and returns the old state if no updates are made or if an unknown action is encountered; Otherwise return a new state object. You can also use the object. assign function to create a new state. You can also use object. assign to create a new state

For example, 🌰

/ / user reducer
const initialUserState = {
    userId: undefined
}

function userReducer = (state = initialUserState, action) {
  switch(action.type) {
    case KUAN_NEED_GRID_FRIEND:
      return Object.assign({}, state, {
        userId: action.payload.data
      })
    default:
      returnstate; }}Copy the code

Before looking at the source code, I give a vivid 🌰 to help you understand.

Xiao Peng wants to ask for a leave to travel. According to the original process, Xiao Peng must apply for leave -> department manager -> Technical director -> HR (one-way process). Xiao Peng’s leave note cannot be directly sent to HR. See below 👇

Hiro saw Xiao Peng asked for a leave to travel, so he wanted to ask for a copy of Xiao Peng’s leave (brother components for data sharing). So what should he do? He couldn’t get the data directly from Xiao Peng, so he had to go to HR through the department manager and technical director, pointing to HR and saying, Please give me a copy of Xiao Peng’s leave form, AND I will ask for leave, too.

When Peng and Hiro want to share data with each other, they can only use the shared boss (HR).

When we use redux, it becomes this cock like 👇 understand button 1, do not understand button eyeballs

Starting with the source code

Dubious!!!!!! And to my most hated source code interpretation, because the source code is too difficult to speak, not the source code is difficult, but how to speak more difficult, after all, I understand and understand the Redux, is not necessarily correct, at the same time I do not want to directly post a lot of code up, you do not want to see the source code to see this article ~

But there is no way, long live understanding. Fortunately, redux source files are relatively few, we work together to give!

🎉 directly look at the source, github stamp here, we can see such a file architecture

├ ─ ─ utils │ ├ ─ ─ actionTypes │ ├ ─ ─ isPlainObject │ ├ ─ ─ warning │ └ ─ │ ├ ─ ─ applyMiddleware ├ ─ ─bindActionCreatorts ├── Heavy Exercises ── index. Js ├─Copy the code

Don’t you? Say many turn left out of the door do not send. Look at the source code to start with index.js, follow the camera, let’s see what this file has. There’s nothing really important, just import the file and export it

// index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'. export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }Copy the code

Import createStore from ‘./createStore’, 😯, which I know is one of the core APIS in Redux. Let’s uncover it

CreateStore first

// API
const store = createStore(reducers, preloadedState, enhance)
Copy the code

First look, don’t know what these three parameters mean? Don’t panic, smoke a cigarette first, open Baidu translation, you will know. (Because these three parameters are explained in the source code)

/** * Create a Redux store containing a state tree * The only way to change the data in a store is to call 'dispatch()' on it ** Your app should only have one store, You can use 'combineReducers' to combine several reducer functions into a reducer Function * * @param {Function} Reducer functions given the current state tree and the operations to process, Returns the next state tree * * @param {any} [preloadedState] initial state. You can choose to specify it as the universal Apps server state in, or restore the previously serialized user session. * If you use 'combineReducers' to create the root reducer function, Then it must be an object with the same shape as the 'combineReducers' key * * @param {Function} [enhancer] store enhancer. You can optionally specify it to enhance store third-party features * like Middleware, Time Travel, Persistence, The only Store enhancer that comes with Redux is' applyMiddleware() '* * @returns {Store} Redux Store, which allows you to read status, schedule actions, and subscribe to changes. * /
Copy the code

Now that we know what these three parameters mean, let’s look at its return value, regardless of what it did in between. As mentioned above, the store created by calling the createStore method is an object that contains four methods, so the code must look like this.

// createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false // Whether events are being distributed

  function getState() {
    // ...
    return currentState
  }

  function subscribe(listener) {
    // ...
  }

  function dispatch(action) {
    // ...
    return action
  }

  function replaceReducer(nextReducer) {
    // ...
  }

  function observable() {
    // ...
  }

  dispatch({ type: ActionTypes.INIT })

  // ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable,
  }
}
Copy the code

The sandbox design

These codes, must be able to understand, but have to admire the people who wrote this code ah!! Internal variables are first privatized through closures, and external variables are not accessible within closures. Second, the interface is exposed to the outside world to achieve external access to internal properties.

Isn’t that what a sandbox is? A sandbox is a way for your application to run in an isolated environment without affecting other applications. Our createStore provides internal data security and external access and manipulation through a developed interface. 🐂 🍺 ~

subscribe/dispatch

💥 recommend to go directly to the source file, because there are very detailed comments for each interface ~

Subscribe (); subscribe (); subscribe (); subscribe ()

function subscribe(listener) {... let isSubscribed =true

  ensureCanMutateNextListeners();
  nextListeners.push(listener)

  return function unsubscribe() {
    if(! isSubscribed) {return
    }

    You may not be able to cancel the store listener during the reducer execution
    if (isDispatching) {}

    isSubscribed = false

    // Remove the current listener from the nextListeners
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)}Copy the code

This method registers the listener and returns a method that unregisters the event. Call listener ~ when store.dispatch is called

The idea is really very rigorous. IsSubscribed and isDispatching are defined to avoid accidents, and the incoming lister is also used for type judgment. Considering that some people will unsubscribe, an unsubscribe is also provided.

Dispatch is an action object. The only way you can modify data in a store is through a Dispatch action

function dispatch(action) {
  if(! isPlainObject(action)) { }if (typeof action.type === 'undefined') {}// Dispatch can only be called one by one to determine the status of the call
  if (isDispatching) {
  }

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // Iterate over each listener
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
  return action
}
Copy the code

The try {} finally {} statement below is also a divine operation. In order to ensure the consistency of isDispatch state inside the function, finally will be changed to false. The cow bye ~

From the source comments inside, I also see such a paragraph ~

It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.

You may then call getState() to read the current state tree inside the callback.

This means that when you execute a previously subscribed function listener, you must go to the latest data via store.getState(). Because the subscription function listener takes no parameters, it’s really strict.

bindActionCreators

There is a sentence in Mr. Lao She’s “Four generations under One Roof” : “He thought there was something lovely about the eldest brother, so he decided to strike while the iron was hot and say everything.” Yes, strike while the iron is hot. So while we’re on the subject of Action, let’s go back to bindActionCreators

I don’t know if you have written such code ~

import { bindActionCreators } from 'redux';
import * as pengActions from '@store/actions/peng';
import * as kuanActions from '@store/actions/kuan';
import * as userActions from '@store/actions/user';

const mapDispatchToProps => dispatch= > {
  return{... bindActionCreators(pengActions, dispatch); . bindActionCreators(kuanActions, dispatch); . bindActionCreators(userActions, dispatch); }}Copy the code

So let’s talk about what this bindActionCreators did. First let’s look at the official source notes:

  • Convert the object whose value is Action Creators to an object with the same key
  • Wrap each function asdispatchCall so that they can be called directly
  • Of course you can call itstore.dispatch(MyActionCreator.doSomething)
function bindActionCreator(actionCreator, dispatch) {
  return function (this, ... args) {
    return dispatch(actionCreator.apply(this, args))
  }
}

// bindActionCreators expects an Object to be passed in as actionCreators
export default function bindActionCreators(actionCreators, dispatch) {
  // If only an action is passed in, the function bound to Dispatch is returned via bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeofactionCreators ! = ='object' || actionCreators === null) {}const boundActionCreators = {} // The final export is this object
  for (const key in actionCreator) {
    const actionCreator = actionCreator[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
Copy the code

For example, 🌰 is the name of the Action

Xiao Peng and Hiro both have a need, that is to launch an action to modify the age, originally two irrelevant, well water is not in trouble, so he wrote this code in two great words ~

// pengAction.js
export function changeAge(params, callback) {
  return {
    type: 'CHANGE_AGE',
    params,
    callback,
  }
}

// kuanAction.js
export function changeAge(params, callback) {
  return {
    type: 'CHANGE_AGE',
    params,
    callback,
  }
}
Copy the code

As it happens, the product asked Ah Hua to make a request. When he clicked the button, he changed the ages of Xiao Peng and Hiro. Ava wanted to install B with bindActionCreators and wrote this code

const mapDispatchToProps => dispatch= > {
  return{... bindActionCreators(pengActions, dispatch); . bindActionCreators(kuanActions, dispatch); }}Copy the code

According to our understanding of the bindActionCreators source code, it should look like this: 😯

pengActions = {
  changeAge: action,
}

export default function bindActionCreators(pengActions, dispatch) {
  // ...
  const boundActionCreators = {}

  for (const key in pengActions) {
    // Key is changeAge
    const actionCreator = pengActions[changeAge]
    // ...
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  }
  return boundActionCreators
}
Copy the code

So in the end, this code looks like this

const mapDispatchToProps => dispatch= > {
  return {
    changeAge, / /... bindActionCreators(pengActions, dispatch);
    changeAge / /... bindActionCreators(kuanActions, dispatch);}}Copy the code

You know what the problem is, so how to solve it? In my opinion, you can either use the same actionName, which can be called changePengAge, changeKuanAge, or you can pack a layer of ~

const mapDispatchToProps => dispatch= > {
  return {
    peng: {
      ...bindActionCreators(pengActions, dispatch);
    },
    kuan: {... bindActionCreators(kuanActions, dispatch); }}}Copy the code

combineReducers

The entire application’s state is stored in a single State tree and only exists in a single store

When xiaopeng project was set up for the first time, the requirements were small and state management was convenient. Therefore, they were all put into a reducer, followed by continuous iteration, so data was continuously filled into this reducer.

Typical ass decides head, so one day, maybe an angel made an issue to the Development team of Redux, “Oh, can you provide an API that integrates all my reducer into one reducer, I want to divide the modular management state”

For example, the user module is called userReducer, the commodity module is called shopReducer, and the order module is called orderReducer. Since there are so many reducer, how to merge into one reducer?

So redux provides combineReducers API, it seems that Redux has learned time management well, you see, so many reducer, can integrate together, must have spent a lot of effort ~

Let’s see what combineReducers does. Before that, let’s see how we use this gadget

/ / two reducer
const pengReducer = (state = initPengState, action) = > {}
const kuanReducer = (state = initKuanState, action) = > {}

const appReducer = combineReducers({
  pengReducer,
  kuanReducer,
})
Copy the code
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers) // Get all reducer names

  // 1. The filtered reducers are not key value pairs of function, and the filtered reducer is placed in finalReducers
  const finalReducers = {}
  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)

  // 2. Filter again and check whether the reducer value is valid
  let shapeAssertionError: Error
  try {
    // assertReducerShape is used to traverse the Reducer in finalReducers and check whether the state passed into reducer is valid
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // 3. Return a function
  return function combination(state, action) {
    // Strict redux is back online, all kinds of strict checks
    // ...

    let hasChanged = false // This is used to indicate whether the state has been updated
    const nextState = {}

    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // This is why the reducer function should have the same name as state in the reducer function
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]

      // Save the value returned from the reducer into nextState
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey

      // hasChanged to true if any state is updatedhasChanged = hasChanged || nextStateForKey ! == previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length ! = =Object.keys(state).length
    return hasChanged ? nextState : state
  }
}
Copy the code

This source code is not much is not difficult, follow wide so look down, is not very difficult? That raises the question, why does redux have to return a new state? Can’t we just go back to the old one?

Return a new state

I like Aesop’s fable very much: escape from the trap is more difficult than fall into the trap, yes, reducer also have traps ~ as we all know, reducer must be a pure function, there are some small friends confused, this TM how to add a knowledge point, don’t care, I am not going to talk more. Self baidu ~

Let’s have a look. In general, how do we write reducer

function pengReducer(state = initialState, action) {
  switch (action.type) {
    // This way
    case 'CHANGE_AGE':
      return {
        ...state,
        age: action.data.age,
      }
    // Either way
    case 'ADD_AGE':
      return Object.assign({}, state, {
        age: action.data.age,
      })
  }
}
Copy the code

Suppose, instead of writing it this way, we modify state directly, instead of returning a new state, what would be the result

function pengReducer(state = initialState, action) {
  switch (action.type) {
    // Either way
    case 'CHANGE_AGE':
      state.age = action.data.age
      return state
  }
}
Copy the code

When we trigger the action, you’ll say: Gosh, why didn’t the page change…

Going back to our source code, we can look at ~

const nextStateForKey = reducer(previousStateForKey, action)
Copy the code

The state was obtained after the reducer execution. It was not a key, it was a state. Then, the line of code was continued

hasChanged = hasChanged || nextStateForKey ! == previousStateForKeyCopy the code

It is a shallow comparison method to compare whether the old and new objects are consistent. Therefore, when the reducer directly returned the old state object, Redux thought that there was no change, so the page was not updated.

That’s why! To return the old state, you need to return a new state reason. As we all know, in JS, comparing two objects is only a deep comparison, however, in real application code is very large, very expensive performance, and if your object nesting is good enough, then need to compare the number of times

So Redux takes a more “euphemistic” solution: return a new object whenever anything changes, and return the old object when nothing changes

applyMiddleware

The hardest part of redux source code is middleware. Before we talk about it, let’s talk about some interesting things

React: 💗 functional programming ~

Functional programming

  1. Functions are first-class citizens

In JS, a function can be passed in as a variable, assigned to a variable, or even returned as a function.

const func = function () {}

// 1. As a parameter
function demo1(func) {}

// 2. Assign to another variable
const copy_func = func

// 3. The result of function execution is function
function demo2() {
  return func
}
Copy the code
  1. Data are Immutable.

In functional programming languages, data is immutable. Once all data is created, it cannot change its value. If it is changed, it can only create a new data.

In redux, it is not possible to change the value of state directly, but only to return a new state ~

As an added bonus, the following two quotes Are from Dan Abramov’s blog: How Are Function Components Different from Classes?

In React, we read data from this.props. XXX. Why can we get the latest examples? It’s not that props changed. In React, props are immutable. They never change. Yet this is, and always will be, mutable.

That’s why the React class component this exists. React itself changes over time so that you can get the latest instances in the render method as well as in the lifecycle method.

  1. The function takes only one argument

How to understand, everyone estimates to have written a long time of many parameters, see this meng ah, I also meng, but this is the rules, no rules, not a square ~

So when you look at middleware code, you shouldn’t be surprised, like this line of code

const middleware = (store) = > (next) => (action) = > {}
Copy the code

Put it in a form we can understand, which is:

const middleware = (store) = > {
  return (next) = > {
    return (action) = >{}}}Copy the code

There’s a question here, holy crap, doesn’t it depend on three parameters, can you write it like that?

const middleware = (store, next, action) = > {}
Copy the code

💐 just you happy! You’re happy, but functional programming is all about taking one argument, that’s the rule, okay? In my town, you’re gonna have to play dumb!

Combination compose

For compose, let’s take a look at the code:

const compose = (f, g) = > {
  return (x) = > {
    return f(g(x))
  }
}

const add = function (x) {
  return x + 2
}

const del = function (x) {
  return x - 1
}

// Use combination function, 🧬 gene mutation, strong combination
const composeFunction = compose(add, del)(100)
Copy the code

Guess what, execute composeFunction to print? Answer right, give oneself drum palm 👏

Well, I’ve taught you the most powerful ninjutsu: the functional programming term compose

The onion model

There is a small partner meng circle here again, how to a knowledge point? Don’t panic. Hiro Jong can give you a quick rundown? For example, the compose function is composed. What does the composition function have to do with the Onion model?

The Onion model is essentially layers of processing logic, which in the world of functional programming means using functions as processing units. First of all, let’s go to 🌰 to help you understand ~

let middleware = []
middleware.push((next) = > {
  console.log('A')
  next()
  console.log('A1')
})
middleware.push((next) = > {
  console.log('B')
  next()
  console.log('B1')
})
middleware.push((next) = > {
  console.log('C')})let func = compose(middleware)
func()
Copy the code

Guess what the print order is? Yes, the print result is: A -> B -> C -> B1 -> A1

Oh, good. I think I’m starting to feel something. When the program runs to next(), it pauses the current program and goes to the next piece of middleware, only to return to the next piece of middleware.

These two pictures should be the old picture, and it is the picture that the conversation to the onion mode will stick, just like you drink, must match peanut (don’t ask why, ask is the rule)

If we look at this diagram, it is interesting that there are two entries into the same middleware, and only after all the first middleware execution, the last middleware is returned in turn. You taste, you fine taste ~

The source code interpretation

Without further ado, I’m not going to cover any of the other ninjuti requirements for functional programming, but let’s take a look at some of the crazy things applyMiddleware does

export default function applyMiddleware(. middlewares) {
  return (createStore) = >(reducer, ... args) => {conststore = createStore(reducer, ... args)let dispatch: Dispatch = (a)= > {}

    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

The code is extremely short, so let’s see what it does. First it returns an anonymous function that takes createStore, and then it returns another reducer… An anonymous function with args (initState, enhancer) as an argument, and then a chain is defined, which is interesting.

const chain = middlewares.map((middleware) = > middleware(middlewareAPI))
Copy the code

We first skin the incoming middlewares and inject the middleware with our defined middlewareAPI, so each of our middleware contexts is Dispatch and getState. Why? Why inject these two things?

  • GetState: This allows each layer of onion to obtain its current state.

  • Dispatch: In order to pass the operation to the next onion

(next) => (action) => {… } is an array of functions returned by the middleware after being stripped. Then we inject with store.dispatch as the parameter ~ compose a call chain by combining the higher-order functions stripped from the middleware array. Once called, all functions within the middleware will be executed.

// Maybe you can understand it better in this form
function compose(. chain) {
  return store.dispatch => {
    // ...}}Copy the code

Redux of the composer

If we call a chain, see see will call a chain

export default function compose(. funcs) {
  if (funcs.length === 0) {
    return (arg) = > arg
  }

  if (funcs.length === 1) {
    return funcs[0]}return funcs.reduce((a, b) = >(... args) => a(b(... args))) }Copy the code

Compose compose compose compose compose compose compose compose compose compose compose compose compose

(a, b) => (. args) = >a(b(... args))// Ordinary people can understand
(a, b) => {
  return(... args) {returna(b(... args)) } }Copy the code

Two words, cowhide 🐂🍺 have to sigh, as expected is big guy. So let’s go through what this is all about.

  • Throw out the first question? Quick answer,What is dispatch for?

🙋 I will I will dispatch is used to distribute actions good so we can get the first function

(store.dispatch) => (action) = > {}
Copy the code

The problem arises when, for compose, a group of functions with the same structure are composed by a bunch of operations, which eventually merge into a single function.

  • This raises the second question, which is to passdispatch, and to passactionSo what do we do? Higher order functions
middleware = (store.dispatch, store.getState) = > (next) => (action) = > {}
Copy the code

Ok, so what is next? Next passed into the middleware was actually store.dispatch, and the odd problem arose again

  • Question three, how do we get each middleware to hold the final dispatch

Redux developers take advantage of closures to strongly bind internal dispatches to external ones, MD, 🐂🍺

The demo / / instance
let dispatch = (a)= > {}

middlewares.map((middleware) = >
  middleware({
    getState,
    dispatch() {
      return dispatch
    },
  })
)
Copy the code

So you should be able to understand the essence of this code in the source code?

// Real source code
let middlewareAPI = {
  getState: store.getState,
  dispatch: (action, ... args) = >dispatch(action, ... args), }If you write the middlewareAPI to middleware, it's equivalent to that
const chain = middlewares.map((middleware) = > middleware(middlewareAPI))
Copy the code

And then what do we need to do next? For example, compose is a function for compose, which is composed. For example, compose is a function for compose. Just call store.dispatch

// Real source codedispatch = compose(... chain)(store.dispatch)Copy the code

This code is essentially equivalent to:

dispatch = chain1(chain2(chain3(store.dispatch)))
Copy the code

Chain1, chain2, and chain3 are the elements of chain. What role does Dispatch play here?

  • Next, bundled with various middleware, says that next is actually store.dispatch
  • Expose an interface to receive actions

As you can see, middleware is basically a dispatch that we customize, and that dispatch pipes the Onion model

what the fuck ! 🐂 🍺 just say dirty words. But here I still have a doubt, hope to see this eldest brother, can solve the doubt ~

I’m left wondering: why is dispatch not written directly as store.dispatch in the middlewareAPI, but referred to as a closure of an anonymous function?

// Why not write....
let middlewareAPI = {
  getState: store.getState,
  dispatch: (action) = > store.dispatch(action),
}
Copy the code

At the end

At this stage, if you haven’t understood anything, you can look at it again. Just look at it and stroke it more often, and you will know

This article has been written for five days, involving a little more knowledge points, it can be said that there are some knowledge points, learn and use now, but the problem is not big, because the extension of knowledge points is not the focus of this article ~ through writing this article, it can be said that I deepened my understanding of Redux. Do not know if there is a small partner like me, want to see the source code, positive just, just however, so toss and turn the battlefield, decided to see some blog article interpretation, and it is too difficult, may be THAT I did not get to the author wants to express the meaning? And for some of these knowledge points, the blogger glosses over. It makes me not know what upstream and downstream is, stiff look, and then immediately forget.

So with this [KT] column, I want to share with you the problems I have encountered and the pits I stepped on on the way to study. Of course, my understanding is not necessarily correct, but I can communicate with you if there is any misunderstanding. Aoli give, don’t say, I go to prepare to do demo, look forward to the next ~

Don’t ask, ask is I also have my own official account, but I will not make it public, the official account was opened in 17 years, at first I wanted to write some friends around the story (relatively non-mainstream) later due to my blog articles were stolen to some official accounts, in order to maintain the original, you know ~

A link to the

  • Hiro’s blog
  • Redux source
  • Redux’s onion model source code analysis and inspiration
  • Dan Abramov Check out his blog
  • 【KT】 Explore React state management
  • [KT] FOR Hox, I wrote a crude component version of dev-Tools

Right, without the original blogger’s consent, forbid to reprint, otherwise I see a, report a, a little hard ~ ~