[the last time] learn its paradigm from Redux source code

Time:2020-9-29

THE LAST TIME

The last time, I have learned

【THE LAST TIME】It has always been a series I want to write, aiming to review the front end.

It’s also a way to make up for deficiencies and share technology.

The author’s collection of articles can be found in

  • GitHub address: nealyang / personalblog
  • Official account:Full stack front end selection

TLT past

  • Thorough understanding of JavaScript execution mechanism
  • this:call、apply、bind
  • A thorough understanding of all JS prototype related knowledge points
  • JavaScript modularization
  • Sorting out the key and difficult points of typescript

preface

normal formThe concept isKuhn’s paradigm theoryIn essence, paradigm is a theoretical system. Kuhn points out that:According to the established usage, paradigm is a recognized model or pattern

And learningReduxIt’s not how complicated the source code is, but his idea of state management, which is really worth learning.

Seriously, the title is really not easy to get, because I wrote this articlereduxThe next one. Two pieces together, is completeRedux

Part 1: from Redux design concept to source code analysis

This paper continues with the first partcombineReducersapplyMiddlewareandcomposeDesign and source code implementation of

As for handwriting, it is actually very simple,Get rid of the strict verification in the source code, it is handwritten on the market。 Of course, in this paper, I also try to expand the remaining few in the form of handwriting evolutionapiIntroduction to the writing method of.

combineReducers

As we know from the last article,newStateIt’s indispatchThrough thecurrentReducer(currentState,action)Got it. thereforestateThe final appearance of the organization depends entirely on what we pass inreducer。 With the continuous expansion of applications,stateIt’s getting more complicated,reduxI thought of divide and conquer. Although it is still a root in the end, each branch is placed in a different file orfuncThen organize and merge. (does modularity exist)

combineReducersNot at allreduxIn other words, this is just an auxiliary function. But I personally like this feature. Its function is to make one by many differentreducerFunction asvalueOfobject, merge into one finalreducerFunction.

evolutionary process

For example, we need to manage such a “huge” one nowstate

[the last time] learn its paradigm from Redux source code

let state={
    name:'Nealyang',
    baseInfo:{
        age:'25',
        gender:'man'
    },
    other:{
        github:'https://github.com/Nealyang',
        Wechatofficialaccount: 'full stack front end selection'
    }
}

Because it’s too big, write onereducerIt’s too hard to maintain inside. So I split it into threereducer

function nameReducer(state, action) {
  switch (action.type) {
    case "UPDATE":
      return action.name;
    default:
      return state;
  }
}

function baseInfoReducer(state, action) {
  switch (action.type) {
    case "UPDATE_AGE":
      return {
        ...state,
        age: action.age,
      };
    case "UPDATE_GENDER":
      return {
        ...state,
        age: action.gender,
      };

    default:
      return state;
  }
}


function otherReducer(state,action){...}

We see the composition of this one for usreducerWe need to do this function

const reducer = combineReducers({
  name:nameReducer,
  baseInfo:baseInfoReducer,
  other:otherReducer
})

So, let’s write one ourselvescombineReducers

function combineReducers(reducers){
    const reducerKeys = Object.keys(reducers);

    return function (state={},action){
        const nextState = {};

        for(let i = 0,keyLen = reducerKeys.length;i<keyLen;i++){
            //Take out the key of reducers, namely name, baseinfo, other
            const key = reducerKeys[i];
            //Take out the corresponding reducers: namereducer, baseinforeducer and otherreducer
            const reducer = reducers[key];
            //Remove the initial state that needs to be passed to the corresponding reducer
            const preStateKey = state[key];
            //Get the state processed by the corresponding reducer
            const nextStateKey = reducer(preStateKey,action);
            //Assign it to the corresponding key of the new state
            nextState[key] = nextStateKey;
        }
        return nextState;
    }
}

Basically, we’re done.

aboutreducerFor more combinations, splits and uses, please refer to megithubDemo of open source front and back end Blogs: react express blog demo

[the last time] learn its paradigm from Redux source code

Source code

export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S

export type ReducersMapObject<S = any, A extends Action = Action> = {
  [K in keyof S]: Reducer<S[K], A>
}

Defines acombineReducersThe parameter type of the function. That’s the one above us

{
  name:nameReducer,
  baseInfo:baseInfoReducer,
  other:otherReducer
}

In fact, it has changedstateOfkey, and thenkeyThe corresponding value is thisReducerThis oneReducerOfstateIt’s the front that takes this outkeyOfstateValue below.

export default function combineReducers(reducers: ReducersMapObject) {
  //Get all the keys, that is, the key of the future state, and also the key corresponding to the reducer at this time
  const reducerKeys = Object.keys(reducers)
  //Filter the corresponding reducer of reducers to ensure that there is nothing wrong with the kV format
  const finalReducers: ReducersMapObject = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  //Get the exact keyarray again
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache: { [key: string]: true }
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError: Error
  try {
    //Verify some basic writing methods of user-defined reducer
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  //The point is this function
  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    
    let hasChanged = false
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      //The above parts are all handwritten contents before us. Nextstateforkey is a new state returned. It can not be undefined
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      //In fact, I'm still confused about whether the judgment will change
      //In theory, the new state after the reducer will not be equal to the prestate
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

combineReducersThe code is very simple. The core code is what we abbreviate above. But I really like this feature.

[the last time] learn its paradigm from Redux source code

applyMiddleware

sayapplyMiddlewareIn fact, it has to be said that,reduxMediumMiddleware。 The concept of middleware is notreduxUnique.ExpressKoaAnd so on. It’s just about solving different problems.

ReduxOfMiddlewareThat’s rightdispatchThe extension, or rewriting, enhancement ofdispatchThe function of!Generally, we can log, error collection, asynchronous call and so on.

Actually aboutReduxOfMiddlewareI think the Chinese document is already very good. Here I will briefly introduce it. Interested can see the detailed introduction: Redux Chinese documents

Middleware evolution

Enhanced logging capabilities

  • Requirements: at each modificationstateMake a note of the changesstate, why it was modified, and the revisedstate
  • Action: every time you modify itdispatchInitiated, so here I just need todispatchAdd a layer of treatment and it’s done for good.
const store = createStore(reducer);
const next = store.dispatch;

/*Rewritten store.dispatch * /
store.dispatch = (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

As above, every time we modify itdispatchYou can write down the log. Because we rewrote itdispatchno

Added error monitoring enhancements

const store = createStore(reducer);
const next = store.dispatch;

store.dispatch = (action) => {
  try {
    next(action);
  } catch (err) {
    console.error ('error report:', ERR)
  }
}

So as mentioned above, we have completed this requirement.

But looking back, how can these two requirements be implemented simultaneously and decoupled?

Think about it, since we areEnhance dispatch。 So, can we pass dispatch as a formal parameter to our enhancement function.

Multi file enhancement

const exceptionMiddleware = (next) => (action) => {
  try {
    /*loggerMiddleware(action);*/
    next(action);
  } catch (err) {
    console.error ('error report:', ERR)
  } 
}
/*Loggermiddleware becomes a parameter and is passed in*/
store.dispatch = exceptionMiddleware(loggerMiddleware);
//Here, next is the purest store.dispatch  了
const loggerMiddleware = (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

So the final use is as follows

const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = (next) => (action) => {
  try {
    next(action);
  } catch (err) {
    console.error ('error report:', ERR)
  }
}

store.dispatch = exceptionMiddleware(loggerMiddleware(next));

But in the above code, we can’t separate middleware from the file, because it depends on the externalstore。 So we putstorePass in!

const store = createStore(reducer);
const next  = store.dispatch;

const loggerMiddleware = (store) => (next) => (action) => {
  console.log('this state', store.getState());
  console.log('action', action);
  next(action);
  console.log('next state', store.getState());
}

const exceptionMiddleware = (store) => (next) => (action) => {
  try {
    next(action);
  } catch (err) {
    console.error ('error report:', ERR)
  }
}

const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));

The above is actually one we wroteMiddlewareIn theory, that’s enough. But! Isn’t it a bit ugly? And reading is not intuitive?

If I need to add a middleware, the call becomes

store.dispatch = exception(time(logger(action(xxxMid(next)))))

That’s what it isapplyMiddlewareWhat’s the point

We just need to know how many middleware there are, and then call them in order internally. No

const newCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);
const store = newCreateStore(reducer)

Handwritten applymiddleware

const applyMiddleware = function (...middlewares) {
  //To override the createstore method is to return a store with an enhanced version (middleware applied) dispatch
  return function rewriteCreateStoreFunc(oldCreateStore) {
  //Returns a createstore for external call
    return function newCreateStore(reducer, initState) {
      //Take out the original store first
      const store = oldCreateStore(reducer, initState);
      //Concept chain = [exception, time, logger] note that this has been passed to the middleware store, and there is a first call
      const chain = middlewares.map(middleware => middleware(store));
      //Remove the original dispatch
      let dispatch = store.dispatch;
      //When the middleware is called, but the array is →. So reverse. Then make a second call on the incoming dispatch. The last one is dispatch func
      chain.reverse().map(middleware => {
        dispatch = middleware(dispatch);
      });
      store.dispatch = dispatch;
      return store;
    }
  }
}

The explanation is all about the code

In fact, there is such a logic in the source code, but the source code implementation is more elegant. He used functional programming composemethod. WatchingapplyMiddlewareBefore the source code, first introduce the method of compose.

compose

actuallycomposeWhat the function does is put thevar a = fn1(fn2(fn3(fn4(x))))This nested call method is changed tovar a = compose(fn1,fn2,fn3,fn4)(x)Method.

composeThe result of running is a function. The parameters passed by calling this function will be used ascomposeThe parameter of the last parameter is called step by step from inside to outside like an onion ring.

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}

Oh, Ho! There is no ~ functional programming is brain burning and direct. So people who love love love very much.

composeIt is a common way to combine functions in functional programming.

The method is very simple. The formal parameter passed in is func [] and if there is only one parameter, the call result will be returned directly. If there are more than one, thenfuncs.reduce((a, b) => (...args: any) => a(b(...args))).

Let’s go straight to the last line

import {componse} from 'redux'
function add1(str) {
    return 1 + str;
}
function add2(str) {
    return 2 + str;
}
function add3(a, b) {
    return a + b;
}
let str = compose(add1,add2,add3)('x','y')
console.log(str)
//Output result '12xy'

[the last time] learn its paradigm from Redux source code

dispatch = compose<typeof dispatch>(...chain)(store.dispatch)The last line of the source code for applymeddleware is this. In fact, even if we handwritten the reverse part above.

Reduce is an array method of Es5, which applies a function to the accumulator and each element in the array (from left to right) to reduce it to a single value. The function signature is:arr.reduce(callback[, initialValue])

So if we look at it this way:

[func1,func2,func3].reduce(function(a,b){
  return function(...args){
    return a(b(...args))
  }
})

So it’s very easy to understand, every timereduceAt the same time,callbackOfaThat’s onea(b(...args))OffunctionThe first time, of courseayesfunc1。 And then there’s the infinite. Finally, I got onefunc1(func2(func3(...args)))Offunction

summary

So look back,reduxIn fact, it’s just these things. The first one isreduxThe core of state management is the idea and way of state management. The second one can be understood asreduxSome of the small ecology. The whole code is only two or three hundred lines. However, this paradigm of state management is still very much for us to think about, learn from and learn from.

exchange of learning

  • Focus on the official account, front desk selection, daily access to good references.
  • Add micro signal: is_ Nealyang (note source), group communication
The official account Personal wechat [is_ Nealyang】
[the last time] learn its paradigm from Redux source code [the last time] learn its paradigm from Redux source code

Recommended Today

Introduction to pyzmq

Introduction to pyzmq ZMQ (hereinafter referred to as ZMQ) is a simple and easy-to-use transport layer, like a socket library framework, it makes socket programming more simple, concise and higher performance. Is a message processing queue library that can be flexibly scaled between multiple threads, kernel and mainframe. ZMQ’s clear goal is to “become part […]