Redux is designed to address the growing complexity of application single-page requirements, which can lead to more cluttered application state management. In a large application, the state of the application includes not only data retrieved from the server, but also data created locally and reflecting the state of the local UI, and Redux is designed to address this complex problem.

Redux, as a one-way data flow implementation, works well with React, especially when the project is large and the logic is complex. The idea of single data flow can clearly control the flow and change of data, and can well separate the business logic from the view logic. The following diagram shows the basic flow of Redux.

This figure shows the basic workflow of Redux framework data, as shown in the figure above. To put it simply, the action is first intercepted by View Dispatch, and then the reducer is implemented and updated into store. Finally, the views will refresh and render the interface according to the changes of store data.

At the same time, as an application state management framework, there are three basic principles to follow when using Redux in order to make your application state management less complex, otherwise your application is prone to undetectable problems. The three principles include: • Single data source The State of the entire application is stored in a State tree and only exists in a single Store. • State is read-only. For Redux, State cannot be changed directly at any time. The only way to change State is to change it indirectly by triggering an action. This seemingly cumbersome way of state modification actually reflects the core idea of Rudux’s state management process, and thus ensures effective state management in large applications. • Application state changes are accomplished through pure functions. Redux uses pure functions to perform state changes. Action indicates the intention to modify state values, while Reducer is the Reducer that actually performs state changes. And Reducer must be a pure function. When Reducer receives Action, the Action cannot directly modify the State value, but returns the modified State by creating a new State object.

The three elements of Redux

Different from Flux framework, Redux framework is mainly composed of Action, Reducer and Store.

Action

Action is a normal JavaScript object, where the type attribute is required to represent the name of the Action, and type is usually defined as a plain string constant. For ease of administration, actions are typically created through Action Creator, a function that returns the action.

In Redux, the change of the State will lead to the change of the View, and the change of the State is triggered by the contact of the View to trigger specific Action. According to the Action Action triggered by the View, different State results will be generated. You can define a function to generate different actions. This function is called Action Creator. Such as:

const ADD_TODO = 'Add event TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

Copy the code

In the code above, addTodo is an Action Creator. However, as your application grows larger, it is recommended that you use a separate module or file to hold your actions.

Reducer

When the Store receives the action, it must return a new State to trigger the View change. The State calculation process is known as Reducer. Reducer is essentially a function that takes Action and the current State as parameters and returns a new State. The format is as follows:

const reducer = function (state, action) {
  // ...
  return new_state;
};

Copy the code

To keep the reducer function pure, do not perform the following operations in the Reducer function: • Modify the incoming parameters; • Perform operations that have side effects, such as API requests and route jumps; • Call impure functions such as date.now () or math.random ()

The initial State of the entire application can be directly used as the default State for the Reducer. Such as:

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      returnstate; }}; Const state = reducer(1, {type: 'ADD',
  payload: 2
});

Copy the code

However, in actual use, the Reducer function does not need to be manually called as above. The store.dispatch method of Store triggers the automatic execution of reducer. To do this, just pass the Reducer into the createStore method when the Store is generated. Such as:

import { createStore } from 'redux';
const store = createStore(reducer);

Copy the code

In the above code, the createStore function takes Reducer as an argument, which returns a new Store and automatically calls Reducer whenever a new Action is sent from store.dispatch to get the new State.

A Store is a place where data is stored. You can think of it as a container. There can only be one Store in the entire application. At the same time, Store also plays the role of linking actions and Reducers together. Store has the following functions: • Maintains application state; • provide the getState() method to getState; • Provide a dispatch(action) method to update state; • Register listeners with subscribe(listener); • Unsubscribe the listener via a function returned by subscribe(listener).

It is very easy to create a Store based on the existing Reducer. For example, Redux provides the createStore function to create a new Store.

import { createStore } from 'redux'
import todoApp from './reducers'// Create a Store using the createStore functionlet store = createStore(todoApp)

Copy the code

The second parameter to the createStore function, which sets the initial state of state, is optional. This can be useful when developing homogeneous applications, keeping the state of the server-side Redux application consistent with the state of the client, and for local data initialization.

let store = createStore(todoApp, window.STATE_FROM_SERVER)
Copy the code

The Store object contains all the data, and if you want the data at any point in time, you need to use state to get it. Such as:

import { createStore } from 'redux'; const store = createStore(fn); Const state = store.getState(); const state = store.getState();Copy the code

According to Redux, one state can only correspond to one view. If the state is the same, the view will be the same. This is also one of the important features of the Redux framework.

At this point, the operation process of Redux is very clear. The following is a summary of the operation process of Redux.

  1. When the user touches the interface, the specific action is captured by calling store.dispatch(Action).
  2. The Redux store then automatically calls the Reducer function, and the Store passes two parameters to the Reducer function: the current state and the received action. Where the Reducer function must be a pure function that returns a new state.
  3. The root Reducer merges the return results from multiple reducer groups into the final application state. In this process, the combineReducers method provided by Redux can be used. When the combineReducers method is used, the actions are passed to each reducer for processing, and after the reducer is processed, the results are returned to the root Reducer for merging into the final application state.
  4. Store calls Store. subscribe(listener) to listen to state changes. Once state changes, store updates will be triggered. Finally, view will refresh the interface according to store data updates.

Redux implementation

1. Create a store

A Store is a data center for Redux, which simply means that all of our data is stored in it and then retrieved from it when we use it on the interface. So first we need to create one of these stores, which can be created using the createStore method provided by Redux.

xport default function createStore(reducer, preloadedState, enhancer) {
  ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
Copy the code

As you can see, createStore takes three arguments and returns an object containing the methods we commonly use, as described below.

getState

GetState is used to obtain the current state in the following format:

function getState() {
    return currentState
  }
Copy the code

Redux internally holds the current store in the currentState variable, whose initial value is the preloadedState we passed in when we called it, which is returned by getState().

subscribe

The listeners are stored in the nextListeners array, and the listeners are inserted into the listeners array and return an unsubscribe function. This function can be used to remove the corresponding callback from the nextListeners. Here is a concrete implementation of this function:

var currentListeners = []
var nextListeners = currentListeners

function ensureCanMutateNextListeners() {
    if(Listeners === currentListeners) {listeners = currentListeners. Slice ()function subscribe(listener) {
    if(typeof listener ! = ='function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      isSubscribed = false

      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
 }

Copy the code

Note that the currentListeners and nextListeners are stored in the above source code because the nextListeners are traversed by the dispatch function, and the customer may call subscribe to insert the listener. To ensure that the nextListeners do not change, a temporary array is required.

dispatch

The reducer corresponding to this action will be called when the View dispatchers an action.

function dispatch(action) {  
  ...
  try {
      isDispatching = trueCurrentState = currentReducer(currentState, Action) // Call the reducer process} finally {isDispatching =false
    }

    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {                   
      var listener = listeners[i]
      listener()
    }
  ...
}
Copy the code

After calling currentReducer, the Dispatch function iterates through the nextListeners array, calling back any function registered with subscribe, so that on each store update, the component can immediately retrieve the latest data.

replaceReducer

ReplaceReducer is to switch the current Reducer. Although the code has only a few lines, it has a very powerful function when it is used. It can realize the function of hot update of the code, that is, call different reducer for the same action according to different situations in the code, so as to get different data.

function replaceReducer(nextReducer) {
 if(typeof nextReducer ! = ='function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.REPLACE })
  }
Copy the code

bindActionCreators

The Purpose of the bindActionCreators method is to simplify the distribution of the action. When an action is fired, the basic call is Dispatch (Action (Param)). This requires writing dispatches at every call, which is cumbersome. BindActionCreators wraps the action in a single layer, returns an encapsulated object, and then calls the Action (Param) directly when launching the action.

react-redux

As a general purpose state management library, Redux not only works with React, but also works with vUE, etc. React-redux encapsulates a layer of redux, which is what React does. The React-Redux library is relatively simple, providing a React component Provider and a method connect. Here’s the easiest way to write react-redux:

import { Provider } from 'react-redux'; // Introduce react-redux... render( <Provider store={store}> <Sample /> </Provider>, document.getElementById('app'));Copy the code

The Connect method is a bit more complicated. It returns a function that creates a package of connect components outside of the WrappedComponent. The Connect component copies all the properties of the WrappedComponent. Subscribe to redux. When store data changes, connect updates state, and then uses mapStateToProps to select the required state. If this state is updated, Connect’s Render method returns the new component.

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  ...
  return functionwrapWithConnect(WrappedComponent) { ... }}Copy the code

This article does not cover react-Redux in detail. You can visit the link below for an introduction to react-Redux and its applications.