preface

Based on the Principle of Redux, this paper realizes a mini-Redux step by step. The main purpose of this paper is to understand the various internal relations between the mini Redux, so this paper will not explain too much about the use of Redux

What is the story

Redux is a predictable state management library that addresses communication between multiple components in React

Without Redux, if two components (not parent-child relationships) need to communicate, multiple intermediate components may be required to do the messaging for them, wasting resources and making the code more complex

Redux proposed a single data source store to store state data. All components can modify the store through action, and can also get the latest state from the store. Redux is the perfect way to communicate between components

Redux design principles

Redux’s three design principles:

  • Single data source
  • The state is read-only
  • Compile reducer using pure functions

Single data source

The react project stores all the states together. A single data source is used to solve the problem of state consistency

In a traditional MVC architecture, you create an infinite number of Models that can listen to each other, trigger events, or even loop or nest trigger events, which are not allowed in Redux

In Redux’s mind, an application will always have a single data source, and this design has some advantages as it makes it easier for developers to debug and observe state changes

There is also no need to worry about the data source object being too large. The combineReducers function provided by Redux can solve this problem

The state is read-only

You cannot change the state data directly. You can only change the state indirectly by sending actions. Indirect change state, which is a key design, is also one of the key points of one-way data flow, for each action occurs, what changes in the state will eventually affect, the order of execution of one after another, etc., are predictable

Compile reducer using pure functions

The concept of a pure function: a function returns a result that depends only on its parameters and is executed without side effects

In REdux, we change the state by defining a reducer. Each reducer is a pure function, which means that it has no side effects and the same input must have the same output

Ps: Modifying external variables, calling the DOM API to modify the page, sending Ajax requests, calling window.reload to refresh the browser, and even console.log to print data are all side effects

I asked you if you were pure

A few basic concepts of Redux

store

A store is a place to store data, it’s an object, and it has several methods

  • GetState () gets the current state
  • Distributed dispatch (action) action
  • Subscribe (handler) listens for changes in data

action

Action can be understood as the action of manipulating data

Action is usually written as follows:

const add = (val) = > {
    return {
        type: 'ADD'.value: val
    }
}
Copy the code

Define what this action is using type. What should be done in reducer

dispatch

The function of Dispatch is to send an action to the Reducer for data processing

General writing:

dispatch({
    type: 'ADD'.value: 1
})
Copy the code

reducer

The reducer is where the data is actually changed. The actions distributed by The Dispatch are finally processed by the Reducer, and each change returns a new state, in order to make the state predictable

middleware

When creating a store, createStore can pass in three parameters. The third parameter is middleware, which is called using redux’s applyMiddleware method, which enhances Dispatch. This allows you to do other things during dispatch, such as recording state changes, asynchronous requests, and so on

Implement a mini-Redux from 0

At the heart of Redux is the createStore function, which returns store, getState, and Dispatch

The general principle of Redux is the publish-subscribe model: action changes to the store are distributed through Dispatch, and view updates are made by subscribes to store changes

createStore

As anyone who has used the createStore method knows, creating a store takes three parameters

/** * create store *@param {*} reducer 
 * @param {*} InitState Initial state *@param {*} Enhancer middleware */
const createStore = (reducer, initState, enhancer) = >{}Copy the code

This function returns several function functions

/** * create store *@param {*} reducer 
 * @param {*} InitialState initialState *@param {*} Enhancer middleware */
const createStore = (reducer, initialState, enhancer) = > {
  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer
  }
}
Copy the code

Let’s implement these methods

The realization of the getState

The getState method returns the current state

let currentState; / / the current state
/** * returns the latest state */
const getState = () = > {
  return currentState;
};
Copy the code

The realization of the subscribe

Subscribe is a higher-order function that returns a function to remove the current listener

let subQueue = []; // Create a listener queue
/** * listen for state changes *@param {*} Listener Function to execute when data changes */
const subscribe = (listener) = > {
  // Put the listener into the listener queue
  subQueue.push(listener);
  // Remove the listening event
  return () = > {
    subQueue = subQueue.filter((l) = >l ! == listener); }; };Copy the code

The realization of the dispatch

The Dispatch method receives an action parameter to execute the reducer, which executes all the listening functions

let currentReducer = reducer;
let isDispatch = false;
/** * issue action and execute all listener functions *@param {*} action 
  */
const dispatch = (action) = > {
  // We use isDispatch as the identifier, so that the next one can be processed only after the previous one is completed
  if(isDispatch) {
    throw new Error('dispatching')}try {
    currentState = currentReducer(currentState, action)
    isDispatch = true;
  } finally {
    isDispatch = false;
  }

  // Execute all listener functions
  subQueue.forEach((listener) = > listener())
  return action
}
Copy the code

The realization of the replaceReducer

ReplaceReducer replaces the current reducer. When the createStore is executed, a default reducer is received. If you want to change another reducer later, you need to use replaceReducer

/** * Replace reducer *@param {*} reducer* /
const replaceReducer = (reducer) = > {
 // Replace the reducer with the reducer
 currentReducer = reducer;
 // Dispatch a dispatch after the replacement
 dispatch({ type: 'MINI_REDUX_REPLACE' });
};
Copy the code

Dispatch after the replacement is to initialize the new Reducer

The full version createStore

There is a lot to understand and implement, so I won’t write about middleware in this article

/** * create store *@param {*} reducer
 * @param {*} InitialState initialState *@param {*} Enhancer middleware */
const createStore = (reducer, initialState, enhancer) = > {
  let currentState; / / the current state
  let subQueue = []; // Create a listener queue
  let currentReducer = reducer;
  let isDispatch = false;

  if (initialState) {
    currentState = initialState;
  }

  /** * returns the latest state */
  const getState = () = > {
    return currentState;
  };

  /** * listen for state changes *@param {*} Listener Function to execute when data changes */
  const subscribe = (listener) = > {
    // Put the listener into the listener queue
    subQueue.push(listener);
    // Remove the listening event
    return () = > {
      subQueue = subQueue.filter((l) = >l ! == listener); }; };/** * issue action and execute all listener functions *@param {*} action* /
  const dispatch = (action) = > {
    // We use isDispatch as the identifier, so that the next one can be processed only after the previous one is completed
    if (isDispatch) {
      throw new Error('dispatching');
    }

    try {
      currentState = currentReducer(currentState, action);
      isDispatch = true;
    } finally {
      isDispatch = false;
    }

    // Execute all listener functions
    subQueue.forEach((listener) = > listener());
    return action;
  };

  /** * Replace reducer *@param {*} reducer* /
  const replaceReducer = (reducer) = > {
    if (reducer) {
      // Replace the reducer with the reducer
      currentReducer = reducer;
    }
    // Dispatch a dispatch after the replacement
    dispatch({ type: 'MINI_REDUX_REPLACE' });
  };

  return {
    getState,
    dispatch,
    subscribe,
    replaceReducer,
  };
};

export default createStore;
Copy the code

To get going in a project, it’s not enough to implement a createStore

CreateStore builds a repository and also requires a delivery point (Provider) and a delivery person (connect) to get to the user (component)

The Provider, the connect

First, we need to be clear about the responsibilities of the three of them:

CreateStore: Generates a store that returns a set of functional functions

Provider: Passes the series of functions returned by createStore to each child component

Connect: Associates data from a store to a component

The realization of the Provider

The main purpose of a Provider is to pass data in a Store

// Provider.jsx
import React, { createContext } from 'react';

export const StoreContext = createContext(null);

const Provider = (props = {}) = > {
  const { store, children } = props;
  return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};

export default Provider;
Copy the code

The realization of the connect

Connect is a higher-order component. The second parameter is the component for which the associated data is needed, and returns a new component

Connect is used to associate store data with corresponding components, monitor store changes, and update corresponding components after data changes

// connect.jsx
import React, { useContext, useEffect, useState } from 'react';
import { StoreContext } from './Provider';

const connect = (mapStateToProps, mapDispatchToProps) = > (WrapComponent) = > {
  const ConnectComponent = () = > {
    const { getState, dispatch, subscribe } = useContext(StoreContext);
    const [props, setProps] = useState({
      getState,
      dispatch,
    });

    let stateToProps;
    let dispatchToProps;

    const update = () = > {
      if (mapStateToProps) {
        stateToProps = mapStateToProps(getState());
      }

      if(mapDispatchToProps) { dispatchToProps = mapDispatchToProps(dispatch); } setProps({ ... props, ... stateToProps, ... dispatchToProps, }); }; useEffect(() = > {
      update();
      subscribe(() = >update()); } []);return <WrapComponent {. props} / >;
  };

  return ConnectComponent;
};

export default connect;
Copy the code

conclusion

A basic version of Mini Redux is done, so I have time to export middleware related stuff

This is my study after the relevant content output of a note, there are wrong places, but also ask you iron juice pointed out the old iron fist

Experience online demo: click me click me click me

Github:github.com/isxiaoxin/m…