Article source: github.com/Haixiang612…

Reference the wheels: www.npmjs.com/package/red…

Preface to ridicule

Redux should be a nightmare for many front-end beginners. I still remember when I first got to know Redux, when I just came over from Vue, I felt that There were so many concepts in Redux that it was difficult to write a Hello World.

The document is also very difficult to understand, not because I don’t understand English, but when I look at it, I always think: TMD is saying mud 🐴. It is obvious that the document is trying to teach the novice well, but it turns out that the novice is increasingly frustrated by verbose typography and systematic exposition. The documentation takes one more suffocating step: the redux, React-Redux, and Redux-Toolkit libraries are put together. Damn, your title is Redux documentation. Why don’t you just say Redux? Newcomers may feel like Redux is tailor-made for React like Vuex, but it’s not.

Redux and React

Redux has nothing to do with React.

Look at the beginning of Redux’s official website: “A Predictable State Container for JS Apps”. “Vuex is a state management pattern + library for vue.js applications”.

Where does the word “react” appear?

The positioning is inherently different: Redux is just an event hub (event bus, whatever you want to call it) for JS Apps. In addition to the event center, Vuex is also for vue.js applications.

What problem was solved

To reunderstand Redux, let’s first understand what Redux is and what problems it solves.

In a nutshell:

  • Create an event center with some data calledstore
  • Provides external read and write operations, calledgetStatedispatchTo modify data by distributing events calleddispatch(action)
  • Add listener, every time dispatch data changes, trigger listener, achieve the effect of monitoring data changes, calledsubscribe

Redux is originally a super simple library, but the documentation has inadvertently made it complex, making it difficult for beginners to start, and word of mouth has it that Redux is difficult and complex. In fact, Redux is not difficult, simple batch.

Don’t believe it? Let’s write a complete Redux.

createStore

This function creates an Object that holds data and provides read and write methods. The implementation is as follows:

function createStore(reduce, preloadedState, enhancer) {
  let currentState = preloadedState // Current data (status)
  let currentReducer = reducer // Calculate new data (state)
  let isDispatching = false // Whether it is at dispatch

  / / for the state
  function getState() {
    if (isDispatching) {
      throw new Error("So we're still dispatching, but we can't get state.")}return currentState
  }

  // Distribute the action function
  function dispatch(action) {
    if (isDispatching) {
      throw new Error("Still dispatching, I can't.")}try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    return action
  }

  return {
    getState,
    dispatch
  }
}
Copy the code

I’m going to store my data in currentState. GetState returns the current data. CurrentState is modified by using the Reducer in the Dispatch to calculate new data (state).

IsDispatching is also used above to prevent the problem of operating the same resource in the case of multiple Dispatches.

If someone doesn’t pass you preloadedState, currentState will start as undefuned, undefined as state won’t work. To solve this problem, we can dispatch an action directly when createStore is created. This action does not match all cases in the Reducer, so the reducer returns the initial value to achieve state initialization. This is why the reducer’s switch-case default must return state instead of doing nothing.

// Generate a random string. Note that 36 of toString(36) is the cardinality
const randomString = () = > Math.random().toString(36).substring(7).split(' ').join('. ')

const actionTypes = {
  INIT: `@@redux/INIT${randomString()}`.// Append random strings to duplicate names
}

function createStore(reduce, preloadedState, enhancer) {.../ / for the state
  function getState() {... }// Distribute the action function
  function dispatch(action) {... }/ / initialization
  dispatch({type: actionTypes.INIT})

  return {
    getState,
    dispatch
  }
}
Copy the code

Then we can use our Redux

const reducer = (state, action) = > {
  switch (action.type) {
    case 'increment':
      return state + action.payload
    case 'decrement':
      return state - action.payload
    default:
      return state
  }
}

const store = createStore(reducer, 1) Dispatch @@redux/INIT to initialize state with or without an initial value

store.dispatch({ type: 'increment'.payload: 2 }) / / 1 + 2

console.log(store.getState()) / / 3
Copy the code

IsPlainObject and kindOf

Redux requires action to be normal. So we also need to determine if it is not a normal object, throw an error and say what type the action is at that time.

// Distribute the action function
function dispatch(action: A) {
  if(! isPlainObject(action)) {// Is a pure object
    throw new Error('Is not pure Object, is a similar${kindOf(action)}What `) // No, it is something like XXX}... }Copy the code

Here isPlainObject and kindOf are available from NPM is-plain-object and kind-of. Both package implementations are simple. Is it like: Huh? Is this it? Tens of thousands of downloads for such a small package?? I can do it myself. Yes, front-end development is so boring, write such a small package can be a bang and red, only difficult that year also won’t JS failed to win the initiative 😢.

Here we use the NPM package, implement a wave of their own:

Typeof obj === ‘object’, but typeof null is also object. This is because when we first implemented JS, Typeof null === null; typeof null === null; You can refer to it here. The other point is that the prototype bond only has one layer.

const isPlainObject = (obj: any) = > {
  // Check the type
  if (typeofobj ! = ='object' || obj === null) return false

  // Check if generated by constructor
  let proto = obj
  while (Object.getPrototypeOf(proto) ! = =null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

export default isPlainObject
Copy the code

The other kindOf function is much more complicated, judging Array, Date, Error and other objects in addition to simple Typeof values.

const isDate = (value: any) = > { // Is it Date
  if (value instanceof Date) return true
  return (
    typeof value.toDateString === 'function' &&
    typeof value.getDate === 'function' &&
    typeof value.setDate === 'function')}const isError = (value: any) = > { // Is there an Error
  if (value instanceof Error) return true
  return (
    typeof value.message === 'string' &&
    value.constructor &&
    typeof value.constructor.stackTraceLimit === 'number')}const getCtorName = (value: any) :string | null= > { / / to get
  return typeof value.constructor === 'function' ? value.constructor.name : null
}

const kindOf = (value: any) :string= > {
  if (value === void 0) return 'undefined'
  if (value === null) return 'null'

  const type = typeof value
  switch (type) { // Have literal values
    case 'boolean':
    case 'string':
    case 'number':
    case 'symbol':
    case 'function':
      return type
  }

  if (Array.isArray(value)) return 'array' // Is not an array
  if (isDate(value)) return 'date' // Is it Date
  if (isError(value)) return 'error' // Is there an Error

  const ctorName = getCtorName(value)
  switch (ctorName) { // The constructor reads the type
    case 'Symbol':
    case 'Promise':
    case 'WeakMap':
    case 'WeakSet':
    case 'Map':
    case 'Set':
      return ctorName
  }

  return type
}
Copy the code

The above two functions are not important in learning about Redux, but can provide some inspiration for implementing the two utility functions and writing them out by hand the next time we use them.

replaceReducer

ReplaceReducer replaceReducer replaceReducer replaceReducer You’re only going to use it when you Code Spliting. For example, if there are two JS packages, the first one is loaded with reducer and the second one is loaded with a new reducer. Here combineReducers can be used to complete the merge.

const newRootReducer = combineReducers({
  existingSlice: existingSliceReducer,
  newSlice: newSliceReducer
})

store.replaceReducer(newRootReducer)
Copy the code

There are so many libraries that do dynamic modules and code splitting that we don’t have much use for this API.

It is also very simple to implement, that is, replace the original reducer.

const actionTypes = {
  INIT: `@@redux/INIT${randomString()}`.REPLACE: `@@redux/REPLACE${randomString()}`
}

function createStore(reducer, preloadedState, enhancer) {...function replaceReducer(nextReducer) {
    currentReducer = nextReducer

    dispatch({type: actionTypes.REPLACE} as A) // Reinitialize the state

    return store
  }
  ...
}
Copy the code

The @@redux/REPALCE action is dispatched. Reset the current state.

subscribe

Redux needs to listen for changes in data, which is very Easy — it can trigger all listeners at dispatch.

function createStore(reducer, preloadedState, enhancer) {
  let currentState = preloadedState
  let currentReducer = reducer
  let currentListeners = [] // The current listener
  let nextListeners = currentListeners // Temporary listener collection
  let isDispatching = false

  / / for the state
  function getState() {
    if (isDispatching) {
      throw new Error("So we're still dispatching, but we can't get state.")}return currentState
  }

  // Distribute the action function
  function dispatch(action: A) {
    if(! isPlainObject(action)) {throw new Error('Is not pure Object, is a similar${kindOf(action)}What `)}if (isDispatching) {
      throw new Error("Still dispatching, I can't.")}try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    listeners.forEach(listener= > listener()) // Execute all once

    return action
  }

  // The nextListeners are gathered as temporary listeners
  // Prevent some bugs that occur in the peer dispatching
  function ensureCanMutateNextListeners() {
    if(nextListeners ! == currentListeners) { nextListeners = currentListeners.slice() } }/ / subscribe
  function subscribe(listener: () => void) {
    if (isDispatching) {
      throw new Error("So we're still dispatching, we can't subscribe.")}let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener) // Add a listener

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

      if (isDispatching) {
        throw new Error("So we're still dispatching dispatching, we can't subscribe.")
      }

      isSubscribed = false

      ensureCanMutateNextListeners()

      // Remove the current listener
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null}}/ / initialization
  dispatch({type: actionTypes.INIT})

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

CurrentListeners are used to execute listeners, and nextListeners are an array of temporary listeners used to add and remove listeners. The two arrays are used to prevent some strange bugs when modifying the array arrays, which is similar to the problem solved in the above using isDispatching to operate the same resource.

Subscribe returns an unsubscribe function. This is a common code design: if a function has a side-effect, the return value should be to cancel the side-effect function, such as the useEffect function.

If you subscribe many times, is the listener in the first unsubscribe still the first listener? This is true because the listener and unsubscribe form a closure. Each unsubscribe will always refer to the listener and the listener will not be destroyed.

Examples of use are as follows:

const store = createStore(reducer, 1)

const listener = () = > console.log('hello')

const unsubscirbe = store.subscribe(listener)

/ / 1 + 2
store.dispatch({ type: 'increment'.payload: 2 }) / / print "hello"

unsubscribe()

/ / 3 + 2
store.dispatch({ type: 'increment'.payload: 2 }) // Can't print "hello"
Copy the code

observable

Observable is a concept proposed by TC39, which represents an observable. There is also a subscribe function in it. The difference is that the parameter passed in is Observer, which needs a next function to generate the current state into the next state.

Now that we’ve implemented listening on store data, the store can also be viewed as an observable. Let’s create a function called Observable that returns the implementation of the above observable:

const $$observable = (() = > (typeof Symbol= = ='function' && Symbol.observable) || '@@observable') ()export default $$observable


function createStore<S.A extends Action> (reducer preloadedState, enhancer) {...Support observable/reactive libraries
  function observable() {
    const outerSubscribe = subscribe

    return {
      subscribe(observer: unknown) {
        function observeState() {
          const observerAsObserver = observer
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }

        observeState() // Get the current state
        const unsubscribe = outerSubscribe(observeState)
        return {unsubscribe}
      },
      [$$observable]() {
        return this}}}... }Copy the code

It can be used like this:

const store = createStore(reducer, 1)

const next = (state) = > state + 2 // A function to get the next state

const observable = store.observable()

observable.subscribe({next}) // Subscribe next: 1 + 2

store.dispatch({type: 'increment'.payload: 2}) // 1 + 2 + 3
Copy the code

As you can see from the above, next’s effect is a cumulative effect. The above features are not used by ordinary people, but are mostly used by other libraries, such as redux-Observable.

applyMiddlewares

Now that createStore is almost complete, there is a third parameter, enhancer, that has not been used. This function is primarily used to enhance createStore. CreateStore is passed directly to the current createStore and after Enhance returns a new reducer and a store:

function createStore<S.A extends Action> (reducer, preloadedState, enhancer) {
  if (enhancer) {
    return enhancer(createStore)(reducer, preloadedState)
  }
  ...
}
Copy the code

There are many ways to implement the enhancer function. The most common and officially provided enhancer function is applyMiddlewares. Its purpose is to enhance the Dispatch through multiple middleware, and the Dispatch is a member of the Store, enhancing the store, so this function is an enhancer.

Before implementing applyMiddlewares, we need to know where the concept of middleware comes from. How do you enhance Dispatch? Why do I use applyMiddlewares enhancer?

Let’s start with a simple example: If we want to console. Log every time we dispatch, the easiest way is to change the dispatch:

let originalDispatch = store.dispatch
store.dispatch = (action) = > {  
    let result = originalDispatch(action)  
    console.log('next state', store.getState())  
    return result
}
Copy the code

Note that dispatch is a function that passes in an action and returns it, so result is returned here.

What if we add Logger 2? It might look something like this:

const logger1 = (store) = > {
    let originalDispatch = store.dispatch
    
    store.dispatch = (action) = > {
        console.log('logger1 before')
        let result = originalDispatch(action) // The original dispatch
        console.log('logger 1 after')
        return result
    }
}

const logger2 = (store) = > {
    let originalDispatch = store.dispatch
    
    store.dispatch = (action) = > {
        console.log('logger2 before')
        let result = originalDispatch(action) // Return function for logger 1
        console.log('logger2 after')
        return result
    }
}

logger1(store)
logger2(store)

// logger2 before -> logger1 before -> dispatch -> logger1 after -> logger2 after
store.dispatch(...)
Copy the code

Logger1 and Logger 2 above are called middleware, and they can get the last onestore.dispatchFunction, and then one operation to generate a newdispatch, and assign tostore.dispatchTo enhancedispatch.

It is important to note that while Logger1 is executed before Logger2, dispatch will be executed with

logger2 before -> logger1 before -> dispatch -> logger1 after -> logger2 after
Copy the code

Implementation of middleware content in a “flashback” manner.

If you have more middleware, you can store it in arrays. Initializers can no longer run scripts like the one above, but can be encapsulated as a function called applyMiddlewares:

function applyMiddleware(store, middlewares) {
    middlewares = middlewares.slice()   // Shallow copy array
    middlewares.reverse() // Invert the array

    // Loop to replace dispatch
    middlewares.forEach(middleware= > store.dispatch = middleware(store))
}
Copy the code

I just mentioned that if the middleware is initialized in positive order, it will execute the dispatch in reverse order, so we need to reverse the middleware array here. Reverse changes the array, so it starts with a shallow copy of the array.

There is a problem with the above syntax: changing store.dispatch directly in forEach creates a side-effect. Functionally, we should generate a final dispatch and assign it to store.dispatch.

How do you generate the final dispatch? We can also create a function that passes in an old dispatch and returns a new dispatch. Such as:

const dispatch1 = (dispatch) = >{... }const dispatch2 = (dispatch1) = >{... }const dispatch3 = (dispatch2) = > {...}
...
Copy the code

However, store will not be transmitted in this way, not afraid, rational use of Corrification can perfect solve our problem:

const logger1 => (store) = > (next) = > (action) = > {
    console.log('logger1 before')
    let result = originalDispatch(action)
    console.log('logger 1 after')
    return result
}

const logger2 => (store) = > (next) = > (action) = > {
    console.log('logger2 before')
    let result = originalDispatch(action)
    console.log('logger2 after')
    return result
}

function applyMiddleware(store, middlewares) {
    // Initial dispatch
    let dispatch = (action) = > {
      throw new Error('Still building Middlewares, don't dispatch')
    }

    middlewares = middlewares.slice() // Shallow copy array
    middlewares.reverse() // Invert the array

    const middlewareAPI = {
      getState: store.getState,
      // The initial dispatch is used to prevent dispatch during the build process
      // If you use the above dispatch directly, there will be closure issues. The build will refer to the original dispatch, and there will be some weird bugs
      // So the newly generated function is used
      dispatch: (. args) = > dispatch(args)
    }

    // How to generate the final dispatch?
    const xxx = middlewares.map(middleware= > middleware(middlewareAPI))
    ...
}
Copy the code

To generate a new function like the nesting above, we need to use the reduce function to compose each function in the array head to tail:

function compose(. funcs:Function[]) {
  if (funcs.length === 0) {
    return (arg) = > arg
  }

  if (funcs.length === 1) {
    return funcs[0]}return funcs.reduce((prev, curt) = > (. args:any) = >prev(curt(... args))) }Copy the code

When you pass middleware into compose(logger1, Logger2) one by one, you will hear:

Logger1 (logger1 before Logger2 (logger2 before dispatch -> logger2 after)Copy the code

The structure of the. This is the most powerful part of Redux, the processing of middleware is very elegant, and reducer is used to change the order of function execution even the above reverse is not needed.

Rearrange the above changes and write applyMiddlewares as enhancer:

function applyMiddlewares(. middlewares: Middleware[]) {
  return (createStore) = > (reducer: Reducer, preloadState) = > {
    const store = createStore(reducer, preloadState)

    let dispatch = (action) = > {
      throw new Error('Still building Middlewares, don't dispatch')}const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (. args) = > dispatch(args)
    }

    const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return{... store, dispatch} } }Copy the code

At this point, you’ve mastered the essence of Redux’s essence. That leaves some “miscellaneous fish” functions.

combineReducers

A very boring function that just merges a reducer from a bunch of reducers into a reducer. Such as:

const nameReducer = () = > '111'
const ageReducer = () = > 222

const reducer = combineReducers({
  name: nameReducer,
  age: ageReducer
})

const store = createStore(reducer, {
  name: 'Jack'.age: 18
})

store.dispatch({type: 'xxx'}) // state => {name: '111', age: 222}
Copy the code

How do you merge? Elegantly simple:

function combineReducers(reducers: ReducerMapObject) {
  return function combination(state, action: AnyAction) {
    let hasChanged = false
    let nextState = {}
    Object.entries(finalReducers).forEach(([key, reducer]) = > {
      const previousStateForKey = state[key] // The previous state
      const nextStateForKey = reducer(previousStateForKey, action) // Update to the current state

      if (typeof nextStateForKey === 'undefined') {
        throw new Error('You can't have undefined.')
      }

      nextState[key] = nextStateForKey // Set the latest statushasChanged = hasChanged || nextStateForKey ! == previousStateForKey// Have you changed it?
    })

    Check whether the number of keys on the reducer is the same as that on the state reducer
    hasChanged = hasChanged || Object.keys(finalReducers).length === Object.keys(state).length

    return hasChanged ? nextState : null}}Copy the code

Essentially, it is to execute each reducer in the reducerMapObject and get the new state to update the state under the corresponding key. Of course, the implementation of this function in Redux is not so simple, and it also does a lot of abnormal cases, such as checking whether reducer is a legitimate reducer. What is a legitimate reducer? A: It is legal not to return undefined if the state cannot be found.

const randomString = () = > Math.random().toString(36).substring(7).split(' ').join('. ')

const actionTypes = {
  INIT: `@@redux/INIT${randomString()}`.REPLACE: `@@redux/REPLACE${randomString()}`.PROBE_UNKNOWN_ACTION: () = > `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

function assertReducerShape(reducers: ReducerMapObject) {
  Object.values(reducers).forEach(reducer= > {
    const initialState = reducer(undefined, {type: actionTypes.INIT})
    if (typeof initialState === 'undefined') {
      throw new Error('Cannot be undefined after initial dispatch')}const randomState = reducer(undefined, {type: actionTypes.PROBE_UNKNOWN_ACTION})
    if (typeof randomState === 'undefined') {
      throw new Error('State after chaotic dispatch cannot be undefined')}}}Copy the code

Use dispatch @@redux/INIT and @@redux/PROBE_UNKNOWN_ACTION to determine whether undefuned is returned if a case in the Reducer is not matched. Check the validity of state, action, etc. :

function getUnexpectedStateShapeWarningMessage(
  inputState: object,
  reducers: ReducerMapObject,
  action: Action,
  unexpectedKeyCache: {[key: string] :true}
) {
  if (Object.keys(reducers).length === 0) {
    return 'What do we have to reducer?'
  }

  if(! isPlainObject(action)) {return 'Action is an ordinary Object, and you pass in some nonsense. '
  }

  if (action.type === actionTypes.REPLACE) return // This reducer was invalid because of replaceReducer

  // Collect the non-existent keys in the reducerMapObject
  const unexpectedKeys = Object.keys(inputState).filter(
    key= >! reducers.hasOwnProperty(key) && ! unexpectedKeyCache[key] ) unexpectedKeys.forEach(unexpectedKey= > unexpectedKeyCache[unexpectedKey] = true)

  if (unexpectedKeys.length > 0) {
    return 'None of the following keys are in state:${unexpectedKeys.join(', ')}`}}Copy the code

Here unexpectedKeyCache is a Map, set to true if one of the child states fails; this Map is cached to prevent multiple alarms.

Update combineReducers again:

function combineReducers(reducers: ReducerMapObject) {
  // Check if it is a function
  let finalReducers: ReducerMapObject = {}
  Object.entries(reducers).forEach(([key, reducer]) = > {
    if (typeof reducer === 'function') {
      finalReducers[key] = reducer
    }
  }, {})

  let shapeAssertionError: Error
  try {
    Check whether the reducer return value is undefined
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // Collect keys that do not exist
  let unexpectedKeyCache: {[key: string] :true} = {}

  return function combination(state, action: AnyAction) {
    if (shapeAssertionError) throw shapeAssertionError

    const warningMessage = getUnexpectedStateShapeWarningMessage(
      state,
      finalReducers,
      action,
      unexpectedKeyCache
    )

    if (warningMessage) {
      console.log(warningMessage)
    }

    let hasChanged = false
    let nextState = {}
    Object.entries(finalReducers).forEach(([key, reducer]) = > {
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)

      if (typeof nextStateForKey === 'undefined') {
        throw new Error('You can't have undefined.') } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey })Check whether the number of keys on the reducer is the same as that on the state reducer
    hasChanged = hasChanged || Object.keys(finalReducers).length === Object.keys(state).length

    return hasChanged ? nextState : null}}Copy the code

combineActionCreators

An even more boring function: just execute multiple Action Creators and return some functions () => dispatch(actionCreator(XXX)), such as:

const store = createStore(reducer, 1)

const combinedCreators = combineActionCreators({
  add: (offset: number) = > ({type: 'increment'.payload: offset}), / / actionCreator addition
  minus: (offset: number) = > ({type: 'decrement'.payload: offset}), / / subtraction actionCreator
}, store.dispatch)

combinedCreators.add(100)
combinedCreators.minus(2)
Copy the code

The main “benefit” is the returned combinedCreators’ direct.add(100), where the.add(100) works without being aware of dispatch.

The concrete implementation is as follows:

// Bind an actionCreator
function bindActionCreator(actionCreator, dispatch) {
  return function (this: any. args:any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}

// Bind multiple actionCreator
const combineActionCreators = (actionCreators, dispatch) = > {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators: ActionCreatorsMapObject = {}

  Object.entries(actionCreators).forEach(([key, actionCreator]) = > {
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  })

  return boundActionCreators
}
Copy the code

The code is very simple, just execute the Action Creator for you and dispatch the action that is returned.

The official hope is that you can dispatch the Action directly in one place (such as the parent component, the combineActionCreators) and in another (such as the child component) without needing the Dispatch function.

The ideal is great, but the premise of this feature is that actionCreator is defined, and generally no one takes the time to define actionCreator, it’s direct dispatch.

conclusion

It already implements all the APIS in redux, essentially the same, without cutting corners.

Of course, there are some details, such as whether an argument is a function, whether it is undefined, that are not done. To avoid writing too long, such as affecting the reading experience, the TS type is also simple to define, and many function signatures are not declared. But these are not too important, the type of judgment can be left to TS to do, and TS types do not need too much trouble, after all, this is not a TS tutorial 😆

To summarize what we did:

  • Implement an event bus + data (state) hub
    • getStateGet data (state)
    • dispatch(action)Modifying data (State)
    • subscribe(listener)Add a listener when modifying data, as long asdispatchAll listeners fire in sequence
    • replaceReducerReplace old Reducer with new reducer, ordinary people can not use, forget it
    • observableIn order to cooperate withtc39To be precise, to cooperate with RxJS. Most people can’t afford it. Forget it
    • enhancerIntroduced into existingcreateStoreAfter a mess and return to the enhancedcreateStore, the most common enhancer isapplyMiddlewares. The average person can only use itapplyMiddlewaresJust remember that
  • implementationapplyMiddlewares, and pass a bunch of middleware throughcomposeCombined, the execution process is the “onion ring” model. The purpose of the middleware is to enhance the Dispatch, and a few things are done before and after dispatch
  • implementationcompose, the principle is to take a heap of parameters as the old dispatch, return the new dispatch function array, useArray.reduceCombine, becomemid1(mid2(mid3()))Infinite nesting doll form
  • implementationcombineReducers, the main function is to create a new Reducer from multiple Reducer components. After dispatch, all the Reducer in the map will be executed. When you use multiple substatesSliceI’m going to use it. Forget about the other scenarios
  • combineActionCreatorsAll actionCreators are executed and returned() => dispatch(actionCreator())A function like that. Forget about that

See here, is not feel that Redux is not actually imagined so complex, all the “difficult”, “complex” is just their own to their own Settings, hard just source code to overcome fear 👊