There are plenty of tutorials online for getting started with Redux, but it’s hard to figure out why to use it this way. Today I will write an article about Redux to help you understand why Redux does what it does and what it does internally.

Directory:

  • types
    • actions.ts
    • middleware.ts
    • reducers.ts
    • store.ts
  • utils
    • actionTypes.ts
    • formatProdErrorMessage.ts
    • isPlainObject.ts
    • kindOf.ts
    • symbol-observable.ts
    • warning.ts
  • applyMiddleware.ts
  • bindActionCreators.ts
  • combineReducers.ts
  • compose.ts
  • createStore.ts
  • index.ts

applyMiddleware.ts

Merge all the middleware into an array and execute it one by one as follows:

const store = applyMiddleware([ loggerMiddleware, thunkMiddleware, ...others ])(createStore)(reducer, preloadState)
Copy the code

The source code is as follows:

export default function applyMiddleware (. middlewares) {
  return (createStore) = > (reducer, preloadState) = > {
    const store = createStore(reducer, preloadState)
    let dispatch = () = > {}
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ... args) = >dispatch(action, ... args) }const chain = middlewares.map(middleware= > middleware(middlewareAPI))
    // compose is the compose method in the SRC directorydispatch = compose(... chain)(store.dispatch)return {
       ...store,
       dispatch
    }
  }
}
Copy the code

bindActionCreators.ts

Combining one or more Actions and Dispatches to produce what the mapDispatchToProps needs to produce is intended to simplify writing and reduce the development burden. Usage:

/ / before use
import React, { useCallback } from "react";
import { createStore, bindActionCreators } from "redux";
import { Provider, connect } from "react-redux";

/ / child component
function Child({ msg, onClick }) {
  const onUserClick = useCallback(() = > {
    onClick("I got clicked.");
  }, [onClick]);
  return <div onClick={onUserClick}>Was it clicked? {msg}</div>;
}

// Make the child components use redux
const ChildWrap = connect(
  state= > ({ msg: state.msg }),
  dispatch= > ({
    onClick: payload= > dispatch({ type: "CLICK", payload })
  })
)(React.memo(Child));

export default function App() {
  const store = createStore((state = {}, action) = > {
    switch (action.type) {
      case "CLICK":
        state = { msg: action.payload };
        break;
      default:
        state = {};
        break;
    }
    return state;
  });
  return (
    <Provider store={store}>
      <div className="App">
        <ChildWrap />
      </div>
    </Provider>
  );
}

/ / after use
import * as actionCreators from './actionCreators'

const ChildWrap = connect(
  (state) = > ({ msg: state.msg }),
  (dispatch, ownProps) = > bindActionCreators(actionCreators, dispatch)
)(React.memo(Child));

// actionCreators.js
export const onClick = (payload) = > ({
  type: "CLICK",
  payload
});
Copy the code

The source code is as follows:

/** * action *@actionCreators { Function | Object } If there are multiple actions, you need to pass in the object *@dispatch       { Function }          Store's Dispatch object */
export default function bindActionCreators (actionCreators, dispatch) {
  // If it is a single function
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'funciton') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

/** * process a single action *@param ActionCreators {Function | Object} if more than one action, requires the incoming Object *@param Dispatch {Function} Store's Dispatch object */
export default function bindActionCreator (actionCreator, dispatch) {
  return function (this. args) {
    return dispatch(actionCreator.bind(this. args)) } }Copy the code

combineReducers.ts

Combine the Reducer together. After the reducer is merged, the reducer can call each reducer and merge the results returned by them into a state object. Parameters: an Object Object, the key is reducerName which can be customized, and the value is reducer Return value of the reducer function: call all the incoming reducer, that is, the value of the input parameter Object. Returns a state object with the same structure as the passed argument

import React, { useCallback } from "react";
import { createStore, combineReducers } from "redux";
import { Provider, connect } from "react-redux";

function Child(props) {
  const { msg, onClick } = props
  const onUserClick = useCallback(() = > {
    onClick("sssss");
  }, [onClick]);
  return <div onClick={onUserClick}>11111 {msg}</div>;
}

const ChildWrap = connect(
  // Pay attention to the state. Reduce1 corresponding to the key written in combineReducer
  (state) = > ({ msg: state.reducer1.msg }),
  (dispatch) = > ({
    onClick: (payload) = > dispatch({ type: "CLICK", payload })
  })
)(Child);

export default function App() {
  const reducer1 = (state = {}, action) = > {
    switch (action.type) {
      case "CLICK":
        state = { msg: action.payload };
        break;
      default:
        state = {};
        break;
    }
    return state;
  };

  const reducer2 = (state = {}, action) = > {
    switch (action.type) {
      case "CLICK2":
        state = { msg: action.payload };
        break;
      default:
        state = {};
        break;
    }
    return state;
  };

  const store = createStore(combineReducers({ reducer1, reducer2 }));

  return (
    <Provider store={store}>
      <div className="App">
        <ChildWrap />
      </div>
    </Provider>
  );
}
Copy the code

The source code is as follows:

export default function combineReducers (reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  // Remove the contents of non-methods from the reducer
  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)
  // Assembled together, equals to a large reducer, and the input parameters are the same as the reducer
  return function combination (state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i ++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducerKeys[key]
      const previousStateForKey = state[key]
      letnextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length ! = =Object.keys(state).length
    return hasChanged ? nextState : state
  }
}
Copy the code

compose.ts

A combinatorial function commonly used in functional programming, in which multiple functions are combined into one function and executed in sequence. Personally, I think this function is the most interesting code to look at in the entire redux source code, and can be used outside of Redux for daily development.

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

createStore.ts

I believe that this method is absolutely everyone with redux a method, directly on the source

/** * Create store method **@param Reducer pure function, receives store and Action, returns new store *@param PreloadedState Initialization state *@param Enhancer Store enhancement function, similar to applyMiddleware */
export default function createStore (reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = null
  }
  if (typeofenhancer ! = ='undefined' && typeof enhancer === 'function') {
    return enhancer(createStore)(reducer, preloadedState)
  }

  let currentState = preloadedState
  let currentReducer = reducer
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  /** * Generate shallow copies of currentListeners, which can be used as a temporary list. * /
  function ensureCanMutateNextListeners () {
    if (currentListeners === nextListeners) {
      nextListeners = currentListeners.splice()
    }
  }

  /** * get the latest status */
  function getState () {
    return currentState
  }

  /** * Add a change listener. It will call an action at any time, and some part of the state tree may have changed. You can then call * to read the current state tree in the fetch call with "getState()". * /
  function subscribe (listener) {
    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    
    return function unSubscribe () {
      if(! isSubscribed) {return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null}}/** * The reducer changes the state value. This is the only way to change the state value */
  function dispatch (action) {
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.')}if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')}try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i ++) {
      const listener = listeners[i]
      listener()
    }
    return action
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer: unknown) {
        function observeState() {
          const observerAsObserver = observer as Observer<S>
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this}}}/** * Replace the current reducer */ used for the current calculation
  function replaceReducer (nextReducer) {
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
    return store
  }

  The default of the reducer switch will be executed once
  dispatch({ type: ActionTypes.INIT })

  const store = {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
  return store
}
Copy the code

index.ts

Export the above method

types

Defines the interface and type that need to be used in the whole project. For reasons of length, I will not elaborate any more. If you are interested, you can view the source code

utils

Tool class method, space reasons, no longer detailed, interested can view the source code