This is the second day of my participation in the August Challenge. For more details, see:August is more challenging

The starting

When developing projects using React, you always need a state management tool. In VUE, there is Vuex, while in React, I believe many companies will choose Redux, but Redux is not designed for React. This is not as convenient for us to use, so there are react-Redux, DVA and other solutions.

When using DVA, there is a relatively complete set of solutions, but not all projects will use DVA. With React-Redux, we need to add various middleware for Redux, such as Redux-Thunk, Redux-Promise…… This article mainly implements redux, React-Redux, Redux-Thunk, redux-Promise. The common API of these libraries. Before this, you need to have some reserve knowledge. React and the API implemented in the article must be mastered in common use. In addition, you need to master functional programming.

Redux implementation

Before implementing Redux, we first need to take a look at our usual usage to determine the implementation requirements.

import {createStore, applyMiddleware, combineReducers} from '.. /redux/index'
export function countReducer(state = 0, action) {
  // console.log('countReducer', state, action);
  switch (action.type) {
    case "ADD":
      return state + (action.payload || 1);
    case "MINUS":
      return state - (action.payload || 1);
    default:
      returnstate; }}const store = createStore(combineReducers({counter: countReducer}), applyMiddleware(thunk, redux_promise));
export default store;

Copy the code
On the page we use getState to get the state, Dispatch to change the state, and subscribe to the execution after the change.Copy the code

For Redux we first need to implement createStore, GetState, Diapatch, and SUBSCRIBE are required for the createStore export instance, and applyMiddleWare and combineReducers are also required for the enhanced Store

createState

In this createStore, save and expose state, dispatch the caller to the reducer rules, and subscribe to the update.

const createStore = (reducer) = > {
  // Store the state variables
  let currentState
  // As a dependency collection, the function iterates through the array to notify page updates
  let currentListens = []
  / / getState implementation
  const getState = () = > {
    return currentState
  }
  // Implement the dispatch function
  const dispatch = (action) = > {
  // Each time we use Dispatch, we call the rules we define in real time to get the output
    currentState = reducer(currentState, action)
    // The state change notification component
    currentListens.forEach(fn= > fn())
  }
  // Subscribe to the implementation
  const subscribe = (callback) = > {
    currentListens.push(callback)
    let length = currentListens.length - 1;
    Return a function that goes out to unsubscribe when the component is unloaded.
    return () = > {
      currentListens.splice(length, 1); }}// getState is null at this time, so we need to manually trigger the initial value
  dispatch({type:"REDUX/XXXXXXXXXX"})
  return {
    getState,
    dispatch,
    subscribe
  }
};

export default createStore

Copy the code

By writing here, you can get a redux without any bonus, which can be used normally. The page can be used normally with the original getState, Diapatch and subscribe, and no middleware can be added at this time.

Middleware applyMiddleware

When using Diapstch, only one object can be passed in to match the reducer defined by us. Therefore, the middleware is an enhancement of Dispatch, so that it can handle a function or promise, and the intermediate effect is simple. For example, redux-Thun only handles functions and redux-Pro Mise only deals with promises, and it is important that each middleware does not contaminate the other. We’re going to do aggregation and currying of programming with functions.

You first need an aggregate function that takes the return value of each function as an argument to the next function, returning a function to be executed. If you don’t understand this function, write a couple of demos to execute and log, and you’ll find it useful (called combinator in the link above).

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

The createStore function needs to be re-created first, because in use, the middleware accepts the parameter as the second parameter of the createStore function, so it is added at the top of the createStore function

const createStore = (reducer, enhancer) = > {
// If the middleware is not passed in, we can execute as usual
  if(enhancer) {
  // We need to use the original createStore function in the middleware function to get the normal store,dispatch.... And the reducer
    return enhancer(createStore)(reducer)
  }
  ......
}
Copy the code

There may be more than one piece of middleware in applyMiddleWare, so let them work separately and return the results to the next piece of middleware without any interaction.

const applyMiddleware = (. middlewares) = > {
Middlewares is the middleware we passed into the createStore
  return createStore= > reducer= > {

    / / get a store (with getState in store, dispatch, subscribe)
    const store = createStore(reducer);

    let dispatch = store.dispatch;
    const midApi = {
      getState: store.getState,
      // Since there is more than one middleware, incoming dispatches may interfere with each other
      // Passing it in as a function causes the dispatch to be followed by a layered dispatch with no interaction between the middleware
      dispatch: (action) = > {
        return dispatch(action)
      }
    }
    const middlewareChain = middlewares.map(middleware= > middleware(midApi))

    // Reassign a function because there is more than one middleware so it needs to be executed like the Onion model. The last argument received by the middleware will be the store.dispatch passed indispatch = compose(... middlewareChain)(store.dispatch)// Make Dispatch a processed aggregate function.
    return {
      ...store,
      dispatch
    }
  }
}
Copy the code

For middleware writing is relatively simple, mainly for functional programming understanding, can use the official and this article written middleware and redux project mix.

redux-thunk

export default function thunk({getState, dispatch}) {
  return next= > action= > {
    console.log('thunk', next, action);
    if(typeof action === 'function') {
      // After the action is called here, the actions will call dispatch internally and cause all middleware to re-execute
      return action(dispatch, getState)
    }
    returnnext(action); }}Copy the code

redux-promise

export default function redux_promise({getState, dispatch}) {
  // Each time you use the compose aggregation function, you pass in the next middleware action to be performed
  return next= > action= > {
    console.log('promise', next, action);
    // After calling action.then, the actions will call Dispatch internally and cause all middleware to re-execute
    return isPromise(action)? action.then(dispatch): next(action)
  }
}

function isPromise(action) {
  return action instanceof Promise;
}

Copy the code

Performing next() at the top indicates that the middleware has no processing and goes to the next middleware. If there is processing, the diapatch change action is called, so you need to go through all the middleware again to process.

combineReducers

In addition, combineReducers is often used when it is used. Students who suddenly forget can search it. I believe that the memory will come to mind in a flash.

This API is relatively simple, I believe a look to understand, but also can be used normally.

export default function combineReducers(reducers) {
  return (state = {}, action) = > {
    let nextState = {}
    let hasChanged = false
    Object.keys(reducers).forEach(item= > {
      letreducer = reducers[item] nextState[item] = reducer(state[item], action) hasChanged = hasChanged || nextState[item] ! == state[item] })returnnextState; }}Copy the code

React-redux: React-Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux: Redux

react-redux

As before, analyze what we need to implement before writing. First of all, we wrap our component with provider in React-Redux to ensure that our component can use the state before using it.

The connect high-level component is used when using a class component. MapStateToPrpos and mapDispatchToProps are accepted for embedding into props.

The function component uses useSelector to get the state and useDispatch to get the function that modifiers the state.

Provider

The first step is to implement the Provider. It accepts a store parameter. The store is the state created using the createStore, and the Context is used to manage the state inside the Provider.

// Pass the store by context
const Context = createContext()


export const Provider = ({ store, children }) = > {
  return (
      <Context.Provider store={store} >
        {children}
      </Context.Provider>)}Copy the code

connect

MapStateToPrpos and mapDispatchToProps. MapStateToPrpos and mapDispatchToProps. MapStateToPrpos and mapDispatchToProps Component returns.


export const bindActionCreators = (creators, dispatch) = > {
  let obj = {}
  // Core logic
  for (const key in creators) {
    obj[key] = bindActionCreator(creators[key], dispatch)
  }
  return obj;
}

const bindActionCreator = (creator, dispatch) = > {
  // Return a function that passes arguments to the actual Dispatch for execution
  return (. args) = > dispatch(creator(args))
}

// forceUpdate website in mock components recommends that the first parameter defined is executed every time the second function is called (rule)
const useForceUpdate = () = > {
  const [, forceUpdate] = useReducer(x= > x + 1.0)
  return forceUpdate;
}
export const connect = (mapStateToProps, mapDispatchToProps) = > (Cmp) = > props= > {
  // Refresh is called every time the store is updated by passing in store.subscribe
  const forceUpdate = useForceUpdate()
  const store = useContext(Context)
  const { getState, dispatch, subscribe } = store;
  // Get the required state
  const stateProps = mapStateToProps(getState())

  // Retrieve the data returned by mapDispatchToProps
  // mapDispatchToProps can be used in either function or object functions
  // In the object is needed to take the object out and return each item with the dispatch package
  let dispatchProps = {}
  if (typeof mapDispatchToProps === 'object') { dispatchProps = { dispatch, ... bindActionCreators(mapDispatchToProps, dispatch) } }else if (typeof mapDispatchToProps === 'function') { dispatchProps = { dispatch, ... mapDispatchToProps(dispatch) } }// Subscribe to updates
  useLayoutEffect(() = > {
    const unSubscribe = store.subscribe(() = > {
      forceUpdate()
    })
    return () = > {
    // Unsubscribe when the component is uninstalled
      if (unSubscribe) {
        unSubscribe()
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [store])
  // Output the output after the injection of the receiving component propr
  return (
    <Cmp {. props} {. stateProps} {. dispatchProps} / >)}Copy the code

So connect is done, and then there are the last two functions, useSelector and useDispatch.

useSelector/useDispatch

export const useDispatch = () = > {
  const store = useContext(Context);
  // Just return the dispatches in the store
  return store.dispatch;
}

export const useSelector = (selector) = > {
  const forceUpdate = useForceUpdate()
  const store = useContext(Context)
  const {getState} = store
  // Subscribe to update this is easy to implement. If you use the useSelecot multiple times within a component, you will have repeated refresh problems
  useLayoutEffect(() = > {
    const unSubscribe = store.subscribe(() = > {
      forceUpdate()
    })
    return () = > {
      if (unSubscribe) {
        unSubscribe()
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [store])
  // Execute the passed function as an argument, and return the status
  const state = selector(getState())
  return state;
}
Copy the code

Here is the completion of the implementation, after testing can be mixed with the official library, to this end, I hope to help you. Source code in the implementation of more exciting, waiting for your exploration, refueling.

Online experience site click to arrive.

The last

I am 007 front cutters, thank you for reading. This article is all aspects of personal understanding after learning, if there are mistakes and flaws, thank you for giving corrections. Helpful words please ❤️ follow + like + collect + comment + forward ❤️