preface

Exploring the principles of Redux and Mobx started with me, starting with this article!

so

A programmer’s career lasts about ten years, which is only one tenth of a human life span. Front-end projects are part of your life and work, but you are all of it. You are its soul. Please put down the long hours of playing games and fiddling at work. Learn more to accompany your project in the most perfect state!

The body of the

This article will look at the Redux and Mobx core Api in detail, but it’s not like you can read it twice, read it three times, and write it once!

knowledge

  • Redux basic use (none)
  • createStore
  • bindActionCreators
  • combineReducers
  • applyMiddleware
  • eggsthunk

  • Basic use of Mobx (none)
  • observable
  • autorun
  • observer

This code is a lot of oh, I this novice read not understand, I read the article, should be able to implement these API and vUE’s response type is similar.

Redux

createStore

This is a core function of create warehouse, dispatch into three core functions, getState, subscribe

The basic use of these three functions is…

// Build the warehouse
const store = createStore(reducer);

const unListen = store.subscribe(() = > {
    console.log("Listener 1", store.getState());
})

store.dispatch(createAddUserAction({
    id: 1.name: "Meet a classmate.".age: 21
}));

unListen(); // Cancel listening

Copy the code

It’s essentially mind mapping

Basic implementation and see the detailed explanation of the code


/** * gets a random string of the specified length *@param {*} length 
 */
function getRandomString(length) {
    return Math.random().toString(36).substr(2, length).split("").join(".")}/** * Check whether an object is a plain-object *@param {*} obj 
 */
function isPlainObject(obj) {
    if (typeofobj ! = ="object") {
        return false;
    }
    return Object.getPrototypeOf(obj) === Object.prototype;
}

// All of the above are helper functions

/** * Implement the createStore function *@param {function} reducer reducer
 * @param {any} DefaultState defaultState */
export default function createStore(reducer, defaultState) {
    let currentReducer = reducer, // The reducer currently in use
        currentState = defaultState; // The current state of the repository

    const listeners = [];  // Record all listeners (subscribers)

    function dispatch(action) {
        / / validate the action
        if(! isPlainObject(action)) {throw new TypeError("action must be a plain object");
        }
        // Verify that the type attribute of the action exists
        if (action.type === undefined) {
            throw new TypeError("action must has a property of type");
        }
        currentState = currentReducer(currentState, action)
        // Run all subscribers (listeners)
        for (const listener oflisteners) { listener(); }}function getState() {
        return currentState;
    }

    /** add a listener (subscriber) */
    function subscribe(listener) {
            if (typeoflistener ! = ='function') {
              throw new Error(
                `Expected the listener to be a function. Instead, received: '${kindOf( listener )}'`
              )
            }
        listeners.push(listener); // Add listeners to the array
        let isSubscribed = false;// Whether it has been removed
        return function () {
            if (isSubscribed) {
                return;
            }
            // Remove the listener from the array
            const index = listeners.indexOf(listener);
            listeners.splice(index, 1);
            isSubscribed = true; }}// When you create a repository, you need to distribute an initial action
    dispatch({
        type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
    })

    return {
        dispatch,
        getState,
        subscribe
    }
}
Copy the code

Implementation of the code base standard Github Redux

It may be more difficult to read since it is officially written in Ts

So filter a lot of details and TS, will be the essence of the thinking presented!

Take you from the inlet to understand the next core function bindActionCreators

bindActionCreators

  • Function used to return directly callabledispatchFunction method of
  • parameter
    • object | function
    • (store.dispatch)
const actionCreators = {
    addUser: createAddUserAction,
    deleteUser: createDeleteUserAction
}

// This can be passed directly to a function
const actions = bindActionCreators(actionCreators, store.dispatch)

actions.addUser({ id: 1.name: "Meet a classmate.".age: 21 })
Copy the code

Use is still very simple, the essence is also a function, very subtle, take a look!

Thinking that is the case, the core function is another getAutoDispatchActionCreator

/ * * *@param {object | function } actionCreators 
 * @param {*} dispatch 
 * @returns * /
export default function (actionCreators,dispatch) {
    // Create a function for the function by calling the automatically distributed action directly
    if(typeof actionCreators === 'function'){
        _getAutoDispatchActionCreator(actionCreators,dispatch)
     // For an object, the traversal object is converted to an object that has created an automatically distributed action creation function
    }else if(typeof actionCreators === 'object') {const result = {}
        Object.keys(actionCreators).forEach((key) = >{
            result[key] = _getAutoDispatchActionCreator(actionCreators[key],dispatch)
        })
        return result
    }else{
        throw new TypeError("actionCreators must be an object or function which means action creator")}}/** * Automatically distribute action creation function *@param {function} actionCreator 
 * @param {any} dispatch 
 * @returns * /
function _getAutoDispatchActionCreator(actionCreator, dispatch){
    return function(. args){
       returndispatch(actionCreator(... args)) } }Copy the code

Is not suddenly bright, take the heat of the iron into the next function combineReducers

combinReducers

The basic idea is this

** * Merge the reducers function * @param {object} reducers * @returns */export default function (reducers) {
    validateReducers(reducers);
    /** * returns the reducer function */
    return function (state = {}, action) {
        const newState = {}; // The new state to return
        for (const key in reducers) {
            if (reducers.hasOwnProperty(key)) {
                constreducer = reducers[key]; newState[key] = reducer(state[key], action); }}return newState; // Return the status}}function validateReducers(reducers) {
    if (typeofreducers ! = ="object") {
        throw new TypeError("reducers must be an object");
    }
    if(! isPlainObject(reducers)) {throw new TypeError("reducers must be a plain object");
    }
    // Verify that the reducer returns undefined
    for (const key in reducers) {
        if (reducers.hasOwnProperty(key)) {
            const reducer = reducers[key];/ / get the reducer
            // Pass a special type value
            let state = reducer(undefined, {
                type:`@@redux/INIT${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined");
            }
            state = reducer(undefined, {
                type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined"); }}}}Copy the code

Compare this to the official Code of Redux’s combinReducers

In general, these are helper functions, and the createStore and the incoming Reducer are the core of mobilizing the entire state data, but Redux provides a core mechanism and middleware is one of the souls of Redux.

How does applyMiddleware work

applyMiddleware

Excellent libraries like Koa and Redux provide intermediate ideas, and when middleware comes out, there must be combinatorial functions

Let’s look at the combination function of Redux

function compose(. funcs:Function[]) {
  return funcs.reduce(
    (a, b) = >
      (. args: any) = >a(b(... args)) ) }Copy the code

Omit some boundary judgments and implement a reducer

On the line

funcs.reduce((a, b) = > (. args) = >a(b(... args)))Copy the code

The function returns the new accumulated value as the reducer b, a,…

Take a look at the applyMiddleware function

import compose from "./compose"
/** * Register middleware *@param  {... any} The essence of all middlewares middleware is a function */
export default function (. middlewares) {
    return function (createStore) { // Give me the function to create the repository
        // The following function is used to create the repository
        return function (reducer, defaultState) {
            // Create the repository
            const store = createStore(reducer, defaultState);
            let dispatch = () = > { throw new Error("You can't use Dispatch right now.")};const simpleStore = {
                getState: store.getState,
                dispatch: (. args) = >dispatch(... args) }// Assign a value to dispatch
            // Based on the middleware array, get an array of dispatch creation functions
            const dispatchProducers = middlewares.map(mid= >mid(simpleStore)); dispatch = compose(... dispatchProducers)(store.dispatch);return {
                ...store,
                dispatch
            }
        }
    }
}
Copy the code

I understand to do these several things

The call returns the second argument thrown to createStore by the function with the above capabilities. Let’s see what happens to createStore

export default function createStore(reducer, defaultState, enhanced) {
    // Enhanced indicates the function returned by Applymiddleware
    if (typeof defaultState === "function") {
        // The second parameter is the return value of the application middleware function
        enhanced = defaultState;
        defaultState = undefined;
    }
    if (typeof enhanced === "function") {
        // Enter the processing logic of applyMiddleWare
        return enhanced(createStore)(reducer, defaultState);
    }

   / /... Same as above

    return {
        dispatch,
        getState,
        subscribe
    }
}
Copy the code
  • performapplyMiddlewareReturns a function that requires a repository function
 applyMiddleware(
        thunk, // Return the middleware function
        logger
)
Copy the code
  • Then the incomingcreateStoreThe repository functions themselves are tricky
  • Get a new repository constructor passed in(reducer, defaultState)
  • The first thing that was passed increateStore(reducer, defaultState)Execution constructs do not use middlewarewarehouse
  • mountstoredispatchThe new object is passed to each middleware function, returning the new onedispatchAn array of
  • Merge all newdispatch, perform the first time
  • returnstoreAnd the newdispatch

This is the basic operation process of middleware some around good good study, day day up

Here’s an Easter egg for youthunkimplementation

The code is a function of just a few lines


function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) = > (next) = > (action) = > {
      if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
      }
      return next(action);
    };
  }
  // Create the Thunk middleware
  const thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;
  
  export default thunk;

Copy the code

Look at the picture and I’ll take you through the code

The red part is the middleware function that throws the Dispatch, getState, and return function to Thunk

The red thread in the figure represents the thunk processing part, which determines whether it is a function or not and then executes it on the next step

Let’s see what next is.

A clear look at next is the dispatch function

The last two actions are a plain plane object type and the asynchronous function thunk processing type

Mobx

Mobx is also a state management tool, but with a different mindset

Similar to Vue responsive principle, with observer pattern collection dependencies implementation

I did not understand the Api, the following is for reference only

observable

This approach clearly builds an observable object

import reaction from './reaction'
export default function observable(target,key,descritor){
    // When a property is decorated with a decorator
    if( typeof key === 'string') {let Reaction = new reaction
     let v =  descritor.initailizer()
     v = createObservable(v)
     return {
       enumerable:true.configurable:true.get(){
        Reaction.collect()
        return v
       },
       set(value){
        v = value
        Reaction.run()
       },
     }
    }
      return createObservable(target)
  } 
  
  function createObservable(val){
      let handle = () = >{
      let Reaction = new reaction
       return {
         get(target,key){
          Reaction.collect()
          return Reflect.get(target,key)
         },
         set(target,key,value){
          let v = Reflect.set(target,key,value)
          Reaction.run()
          return v
         },
       }
      }   
      return deepProxy(val,handle)
  }
  
  function deepProxy(val,handle){
    if(typeofval ! = ='object') return val
  
    // Implement the proxy from the back
    for(let key in val){
      val[key] = deepProxy(val[key],handle)
    }
  
    return createObservable(val, handle())
  }

Copy the code

It is clear that this function implements the following

  • Determines whether the decorator is in use
  • Proxy observes a depth object
  • Trigger dependency collection on GET

reaction

let nowFn = null
let counter = 0
class Reaction {
  constructor(){
    this.id = ++counter
    this.store = {}

  }
  run(){
    if(this.store[this.id]){
      this.store[this.id].forEach(w= >w())
    }
  }
  collect(){
    if(nowFn){
      this.store[this.id] = this.store[this.id] || []
      this.store[this.id].push(nowFn)
    }
  }
  static start(handle){
    nowFn = handle
  }
  static end (){
    nowFn = null}}Copy the code

autorun

  import reaction from './reaction'
  export default function autorun(handle){
    reaction.start()
    handle()
    reaction.end()
  }
Copy the code

Look at Autorun and Reaction

It’s basically a simple dependency collection and triggering

Those of you who know the observer pattern and the Vue dependency collection are familiar with this notation

And the last observer

observer

@param {*} target */ export default function observer(target){let CWM = target.prototype.componentWillMount; target.prototype.componentWillMount = function(){ cwm && cwm.call(this); autorun(()=>{ this.render(); this.forceUpdate(); }}})Copy the code

This only modifies the state of the React class component

contrast

Redux

  • Single data source
  • Status data read only
  • Use pure functions to modify state

Mobx

  • Multiple data sources
  • Observed object
  • Observe rely on
  • Trigger action

The difference:

To compare redux mobx
The core module Action,Reducer,Store, no concept of scheduler ObserverState, Derivations, Actions…
Store There is only one Store, which is separate from the change logic, and a single Stor with the layered Reducer More than one store
state The state is immutable (immutable value recommended) States are usually wrapped as observables, and observers depend on the collection pattern
Programming ideas Follow the idea of functional programming Functional responsive programming
object JavaScript object Observable

conclusion

  • The source code implementation of Redux understands the core ideas of middleware
  • Implement the core of Mobx (and the rest)
  • Compare the advantages and disadvantages of the two state management differences

This article starts making | source Analysis