JS state container - Redux and react Redux and middleware use

Article directory

Basics

What is Redux?

Redux is a JavaScript state container that provides predictable state management. You can build consistent applications that run in different environments.

Install Redux

npm install --save redux
#perhaps
yarn add redux

Core idea

The core idea of Redux is to update state through action.

Action is like an indicator of what happened. Finally, in order to string action and state, some functions are developed, which are called reducers. reducer is just a function that receives state and action and returns the new state.

For large applications, it's impossible to write only one such function, so we write many small functions to manage a part of state separately. Finally, we call these small functions through a large function to manage the state of the whole application.

Three principles

Single data source

The state of the whole application is stored in an object tree, and the object tree only exists in a single store.

console.log(store.getState())
/* output
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
*/

State read-only

The only way to change the state is to trigger action, which is a common object used to describe events that have occurred.

This ensures that neither the view nor the network request can directly modify the state. Instead, they can only express the intention to modify. Because all the changes are centralized and executed in strict order one by one, there is no need to worry about the occurrence of race conditions.

Action s are just ordinary objects, so they can be printed, serialized, stored, later debugged, or played back by tests.

//Define the Action object and trigger the Action through the store.dispatch method
store.dispatch({
  type:'COMPLETE_TODO',
  index:1
})

store.dispatch({
  type:'SET_VISIBILITY_FILTER',
  filter:'SHOW_COMPLETED'
})

Using pure functions to perform modifications

To describe how action changes state tree, you need to write the reducers function

Reducer is just a pure function that takes the previous state and action and returns the new state.

At the beginning, you may only have one reducer. As the application grows larger, you can split it into several small reducers to operate different parts of state tree independently, because reducers are only functions. You can control the order in which they are called, pass in additional data, and even write reusable reducer functions to handle some common tasks.

//1. Import the combineReducers and createStore objects in redux
import {combineReducers,createStore} from 'redux';


//2. Define several small reducer functions to handle specific state (parameters are previous state and action)
function visibilityFilte(state = 'SHOW_ALL' , action){
  switch(action.type){
    case 'SET_VISIBBILITY_FILTER':
      return action.filter
    default:
      return state
  }
 }

function todos(state = [] , action){
  switch(action.type){
    case 'ADD_TODO':
      return [
        ...state,
        {
          text:action.text,
          completed:false,
        }
      ]
    case 'COMPLETED_TODO':
      return state.map((todo,index)=>{
        if(index === action.index){
          return Object.assign({},todo,{
            completed:true
          })
          
        }
        return todo
      })
    default:
      return state     
  }
}

//3. Combine several small reducers through the combinedreducers function.
let reducer = combineReducers({visibilityFilte,todos})

//4. Create a Redux Store object through the reducer function to store the application status
let store = createStore(reducer);

//5. You can subscribe to updates manually or bind events to view layers
store.subscribe(()=>{
  //When the state update triggers here
  console.log(store.getState());
});

//6. Use store to specify action to trigger action to change state,
store.dispatch({
  type:'COMPLETE_TODO',
  index:1
})

store.dispatch({
  type:'SET_VISIBILITY_FILTER',
  filter:'SHOW_COMPLETED'
})

Action

  • brief introduction

    Action is the payload that transfers data from the application (not called View here because it may be the data from the server response, user input or other non View data) to the store. It is the only source of store data. In general, you will pass the action to the store through store.dispatch(). (in short: action is used to describe what happened)

    The action of adding a new todo task is as follows:

    const ADD_TODO = 'ADD_TODO'
    
    {
      type:ADD_TODO,
      text:'Build my first Redux app'
    }
    

    Action is essentially a common object of JavaScript. We agree that a string type type type field must be used within an action to represent the action to be performed. In most cases, type is defined as a string constant. When the application scale is growing, it is recommended to use independent modules or files to store actions.

    import {ADD_TODO,REMOVE_TODO} from '../actionTypes'
    

    In addition to the type field, the structure of the action object is entirely up to you. Refer to Flux standard Action Get advice on how to construct an action.

    At this time, we need to add another action index to represent the action sequence number of the user completing the task. Because the data is stored in an array, we refer to a specific task by subscribing the Index. In the actual project, the unique ID will be generated as the reference ID when creating new data.

    {
      type:TODO_ADD,
      index:5
    }
    

    We should try to minimize the data transfer in action. For example, in the above example, it is better to pass the index than the whole task object

  • Action Creator

    Action creating function is the method to generate action. The two concepts of action and action creation function are easy to mix, so it is better to distinguish them when using them.

    The action creation function in Redux simply returns an action:

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

    This will make the functions created by action easier to migrate and test.

    The dispatch() method can be called directly from store.dispatch() in store, but most of the time you will use the connect() helper provided by react-redux. bindActionCreators() can automatically bind functions created by multiple actions to the dispatch() method.

    Note: we usually use this method (action factory function / action creator) to construct the action object).

  • Source case (actions.js)

    //action type (large projects are usually declared in one component independently, and exported for other components)
    export const ADD_TODO = 'ADD_TODO';
    export const TOGGLE_TODO = 'TOGGLE_TODO';
    export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
    
    //Other constant objects
    export const VisibilityFilters = {
      SHOW_ALL:'SHOW_ALL',
      SHOW_COMPLETED:'SHOW_COMPLETED',
      SHOW_ACTIVE:'SHOW_ACTIVE'
    }
    
    //Create action function and export, return action
    export function addTodo(text){
      return {
        type:ADD_TODO,
        text
      }
    }
    
    export function toggleTodo(index){
      return {
        type:TOGGLE_TODO,
        index
      }
    }
    
    export function setVisibilityFilter(filter){
      return {
        type:SET_VISIBILITY_FILTER,
        filter
      }
    }
    

Reducer

  • brief introduction

    Reducers specifies how the application status responds to actions and sends them to the store. Remember that actions only describes the fact that something happened, not how the application updates the state. (in short: the reducer updates the state according to the action)

    There is only one single reducer function in the whole application: this function is the first parameter passed to createStore. Finally, a single reducer needs to do the following:

    • When the reducer is called for the first time, the value of state is undefined. The reducer needs to provide a default state to handle this situation before the action is passed in.
    • The reducer needs the previous state and dispatch action s to decide what needs to be done.
    • Assuming that you need to change the data, you should create new objects or arrays with the updated data and return them.
    • If there are no changes, you should return the current state itself.

    Note: it is very important to keep the reducer pure. Never do these operations in the reducer

    • Modify the incoming parameters;
    • Perform side-effect operations, such as request and route jump;
    • Call non pure functions such as Date.now() or Math.random().

    Just remember that reducer must be pure. As long as the incoming parameters are the same, the next state calculated by the return must be the same. There is no special case, no side effect, no API request, no variable modification, and only calculation is performed.

  • Action processing

    When Redux executes for the first time, the state is undefined. At this time, we can take the opportunity to set and return the initial state of the application.

    //Introducing VisibilityFilters constant object
    import {VisibilityFilters} from './actions'
    
    //Initialize the state state.
    const initialState={
      visibilityFilter:VisibilityFilters.SHOW_ALL,
      todos:[]
    };
    
    //Define the reducer function
    function todoApp(state,action){
      //If state is defined to return the initialized state
      if(typeof state === 'undefined'){
           return initialState;
         }
       //No action will be processed here, only the incoming state will be returned
      return state
    }
    

    Use ES6 parameter default syntax to simplify code

    function todoApp(state = initialState,action){
        //No action will be processed here, only the incoming state will be returned
        return state;
    }
    

    You can now process action.type set? Visibility? Filter. All you have to do is change the visibilityFilter in state:

    function todoApp(state = initialState,action){
      switch(action.type){
        case 'SET_VISIBILITY_FILTER':
          return Object.assign({},state,{
            visibilityFilter:action.filter
          })
        default:
          return state;
      }
    }
    

    Be careful:

    1. Do not modify state

      A new copy is created using Object.assign(). Do not use the following methods

      Object.assign(state,{visibilityFilter:action.filter}), because it changes the value of the first parameter. You must set the first parameter to an empty object.

    2. Returns the old state in the case of default. When an unknown action is encountered, be sure to return the old state

      Object.assign introduce:

      Object.assign(target,source1,source2...sourceN)yes ES6 Property for merging objects.

      //Object merging: source1, source2 are merged into target.
      const target = { a: 1 };
      const source1 = { b: 2 };
      const source2 = { c: 3 };
      Object.assign(target, source1, source2);
      target // {a:1, b:2, c:3}
      
      //Replacement of attribute with the same name: the value of attribute a in source replaces the value of attribute a in target.
      const target = { a: { b: 'c', d: 'e' } }
      const source = { a: { b: 'hello' } }
      Object.assign(target, source)// { a: { b: 'hello' } }
      
      //Array processing: assign will treat the array as an object with property names 0, 1, 2,
      //So the value 4 of the 0 property of the source array overrides the value 1 of the 0 property of the target array.
      Object.assign([1, 2, 3], [4, 5])// [4, 5, 3]
      
      

      Be careful:

      • The first parameter of this method is the target object, and the later parameters are all the source objects;
      • If the target object has an attribute with the same name as the source object or multiple source objects, the following attributes will overwrite the previous ones;
      • Object.assign is a shallow copy. If the value of a property of the source object is an object, the copy of the target object will get the reference of the object. Any change of the object will be reflected on the target object;
      • Replacement of a property with the same name. For nested objects, once an attribute with the same name is encountered, the processing method of Object.assign is replacement, not addition.
  • Handling multiple actions

    Be careful:

    • Each reducer is responsible for managing only the part of the global state that it is responsible for. The state parameters of each reducer are different, corresponding to the part of state data it manages.

    Last. Redux provides** combineReducers() **The working class is used to generate a function that calls your series of reducers. Each reducer filters out part of the state data and processes it according to their key. Then the generated function merges all the results of the reducers into a large object.

  • Code case (reducers.js)

    import {combineReducers} from 'redux'
    //Import action type constants and functions from actions file
    import {
      ADD_TODO,
      TOGGLE_TODO,
      SET_VISIBILITY_FILTER,
      VisibilityFilters
    } from './actions'
    
    //Destructuring assignment
    const {SHOW_ALL} = VisibilityFilters
    
    //Define the reducer function to display the filter
    function visibilityFilter(state = SHOW_ALL,action){
      switch(action.type){
        case SET_VISIBILITY_FILTER:
          return action.filter
        default:
          return state   
      }
    }
    
    //Define the reducer function to deal with things
    function todos(state = [] , action){
      switch(action.type){
        case ADD_TODO:
          return [
            ...state,
            {
              text:action.text,
              completed:false 
            } 
          ]
        case TOGGLE_TODO:
          return state.map((todo,index)=>{
            if(index ==== action.index){
              return Object.assign({},todo,{
                completed:!todo.completed
              })
             }
            return todo
          })
        default:
          return state   
      }
    }
    
    //Associate multiple customized reducers through the combinedreducer function
    const todoApp = combineReducers(){
      visibilityFilter,
      todos
    }
    
    //Export the Reducer for external use
    export default todoApp;
    

Store

  • brief introduction

    Action is described earlier to describe "what happened", and reducers are used to update state based on action.

    Store is the object that connects action and reducer. The store has the following responsibilities:

    • Maintain the state of the application;
    • Provide getState () method to get state;
    • Provide the dispatch (action) method to update the state;
    • Register the listener through subscribe (listener);
    • The unsubscribe function returned through the subscribe (listener) is used to unregister the listener.

    Again, Redux should have only one single store. When you want to split the data processing logic, you should use the reducer combination instead of creating multiple stores.

  • createStore create Store

    It's very easy to create a Store based on the existing reducers. In the previous section, we used combinedreducers () to merge multiple reducers into one. Now we import it and use it to create Store (reducers):

    import {createStore} from 'redux'
    //Import the defined reducers object
    import todoApp from './reducers'
    //Create Store object
    let store = createStore(todoApp)
    

    The second parameter of createStore() is optional and is used to set the initial state of the state. This is very useful for developing homogeneous applications. The state structure of the server-side redux application can be consistent with that of the client. Then the client can directly use the server-side state received from the network for local data initialization:

    let store createStore(todoApp,window.STATE_FROM_SERVER)
    
  • Initiate Action (index.js)

    After the above steps, we have created action, reducer and store. At this time, we can verify that although there are no pages, because they are pure functions, we only need to call them to judge the return value. Writing tests is that simple.

    import {createStore} from 'redux'
    
    //1. Introduce the defined action
    import {
      addTodo,
      toggleTodo,
      setVisibilityFilter,
      VisibilityFilters} from './actions'
    
    //2. Introduce the defined reducer
    import todoApp from './reduces'
    
    //3. Create a store
    let store = createStore(todoApp)
    
    //4. Trigger action to update state through reducer
    store.dispatch(addTodo('Learn about actions'))
    store.dispatch(addTodo('Learn about reducers'))
    store.dispatch(addTodo('Learn about store'))
    store.dispatch(toggleTodo(0))
    store.dispatch(toggleTodo(1))
    store.dispatch(setVisibilityFilter(VisibilityFilter.SHOW_COMPLETED))
    
    //5. Enable listening state update through subscribe. Note that a function object is returned to log off listening
    const unsubscribe = store.subscribe(()=>{
      console.log(store.getState())
    })
    
    //6. Stop listening for state updates
    unsubscribe();
    
    

Basic structure of State

Redux encourages you to think about your application based on the data you need to manage. Data is your application state.

The top-level state tree in Redux state is usually a common JavaScript object (of course, it can also be other types of data, such as numbers, data or other special data structures, but the top-level values of most libraries are common objects).

Most applications handle a variety of data types, which can be divided into the following three categories:

  • Domain data: data to be displayed, used or modified by application;
  • App state: data specific to an application's behavior;
  • UI state: controls how the UI presents data.

A typical application state will be roughly as follows:

{
  domainData1:{},   //Data domain 1
  domainData2:{},   //Data domain 2
  appState1:{},     //Application status domain 1
  appState2:{},     //Application status domain 1
  ui:{              //UI domain
    uiState1:{},
    uiState2:{},
  } 
}

Use of react Redux

It is emphasized here that there is no relationship between Redux and React. Redux supports React, Angular, Ember, JQuery, and even pure JavaScript.

Although this time, Redux works best with React and Deku, which allow you to describe the interface in the form of state functions. Redux initiates state changes in the form of action.

Install React Redux

Redux does not contain React binding library , which needs to be installed separately.

npm install --save react-redux
#perhaps
yarn add react-redux

Core API explanation

1. Provider

The provider component is the core component provided by react Redux, which provides the Redux Store for internal component access.

Use

Provider components provided through react Redux include other components

//Import Provider component
import {Provider} from 'react-redux';
//Import the created Redux Store object
import store from './store';
//Import components
import CustomComponent from './CustomComponent';

export default class App extends Component{
  render(){
    return(
      <Provider>
      		<CustomComponent/>
      </Provider>
    );
  } 
}
2. connect

The connect component is also the core component provided by react redux. It is used to associate the current component with the Redux Store, so as to map the current component Props with the State and dispatch in the Redux Store through mapStateToProps and mapDispatchToProp functions.

Be careful:

  1. Before using connect(), you need to define mapStateToProps;
  2. Components connected by connect need to be wrapped by Provider components;
  3. mapStateToProps: this function specifies to establish a mapping relationship between the current component Props and Redux store state. Every time the state of the Store changes, the function of all components in the application will be called. If the component is not transferred, it will not listen to the change of Store State, that is to say, the update of Store will not cause the update of UI.
  4. mapDispatchToProps: this function specifies to map the current component props to store.dispatch. If you don't transfer react Redux, the dispatch will be automatically injected into the props of the component (you can use this.props.dispatch(action)).

Use

The components wrapped in the Provider component are connected to the Redux Store through the connect provided by react redux.

//1. Introduce connect
import { connect } from 'react-redux';
class CustomComponent extends Component{
  render(){
    return (
       <View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}>
      		<Button title={'Modify display data'} 
                /**
                 *Indirectly trigger store.dispatch(action) through this.props.btnOnClick
                 *Change the State in the Store.
                 */
                onPress={() => {this.props.btnOnClick();}}/>
          //Get the data in Redux Store State through property mapping this.props.showText.
        	<Text style={{marginTop: 20}}>{this.props.showText}</Text>
      </View>
    );
  }
}

/**
 *2.Create mapStateToProp function
 */
const mapStateToProps = (state) => {
    return {
        /**
         *Map loginText in Redux Store State to the showText property of the current component,
         *Then the current component can obtain the value of loginText stored in the Store State through this.props.showText.
         */
        showText: state.loginText, 
        //Map the loginStatus state in the Redux Store State to the showStatus property of the current component.
        showStatus: state.loginStatus,
    }
};

/**
 *3.Create mapDispatchToProp function
 */
const mapDispatchToProps = (dispatch)=>{
  	return{
      /**
       *changeShowTex: For the custom props attribute function name, the current component uses this.props.changeShowText()
       *You can trigger store.dispatch(action) to update the data in Redux Store State.
       */
      changeShowText:()=>{
        dispatch(action);   //Send the specified action to change the State in the Store
      }
  	}
}

/**
 *4.Connect the current component to the Store of Redux through the connect function. (the current component needs to be wrapped by the Provider component)
 */
export default connect(mapStateToProps,mapDisPatchToProps)(CustomComponent);

Full sample code

To achieve the goal: click the page button (trigger store.dispatch(action)) to change the currently displayed copy information (the copy display information here is stored in the Redux Store State)

  1. Define Action type

    //ActionType.js
    //Landing status
    const LOGIN_TYPE = {
        LOGIN_SUCCESS: 'LOGIN_SUCCESS',
        LOGIN_FAILED: 'LOGIN_FAILED',
        LOGIN_WAITING: 'LOGIN_WAITING',
    };
    export {LOGIN_TYPE};
    
  2. Create Action (tell Reducer what to do)

    //Actions.js
    //Import action types
    import {LOGIN_TYPE} from '../ActionType';
    
    export const loginWaiting = {
        type: LOGIN_TYPE.LOGIN_WAITING,
        text: 'Landing...',
    };
    
    export const loginSuccess = {
        type: LOGIN_TYPE.LOGIN_SUCCESS,
        text: 'Landing successfully...',
    };
    
    export const loginFailed = {
        type: LOGIN_TYPE.LOGIN_FAILED,
        text: 'Landing failed...',
    };
    
  3. Create a Reducer (process the corresponding logic according to the passed action type to return a new state)

    //Reducer.js
    //Import action type
    import {LOGIN_TYPE} from '../ActionType';
    
    //Default state
    const defaultState = {
        loginText: 'Content display area',
        loginStatus: 0,
    };
    
    /**
     *To create a reducer:
     *Change loginText and loginStatus in the Store State according to the current action type,
     *The mapStateToProps function that sends the store.dispatch(action) event component is called back.
     */
    const AppReducer = (state = defaultState, action) => {
        switch (action.type) {
            case LOGIN_TYPE.LOGIN_WAITING:    
                return {
                    loginText: action.text,
                    loginStatus: 0,
                };
            case LOGIN_TYPE.LOGIN_SUCCESS:
                return {
                    loginText: action.text,
                    loginStatus: 1,
                };
            case LOGIN_TYPE.LOGIN_FAILED:
                return {
                    loginText: action.text,
                    loginStatus: 2,
                };
            default:
                return state;
        }
    };
    export {AppReducer};
    
  4. Create Redux Store

    //store.js
    import {createStore} from "redux";
    import {AppReducer} from '../reducers/AppReducer';
    //Create store according to Reducer
    const store = createStore(AppReducer);
    export default store;
    
  5. Use entry

    //App.js
    //Pass the store to the sub component access through the Provider component provided by react Redux
    import React,{Component} from 'react';
    import {Provider} from 'react-redux';
    import store from './store';
    import CustomComponent from '../page/CustomComponent';
    import CustomComponent2 from '../page/CustomComponent2';
    import CustomComponent3 from '../page/CustomComponent3';
    export default class App extends Component{
      render(){
        return(
          <Provider store={store}>
          		<CustomComponent/>    //Sub components
              <CustomComponent2/>   //Sub module 2
              <CustomComponent3/>   //Sub module 3
              ...
          </Provider>
        )
      } 
    }
    

    Above, we use the Provider component provided by react Redux to provide our created store to the subcomponent CustomComponent for access. Next, we will see how to establish a relationship with the Redux Store in the subcomponent and access its State content.

  6. Access to Redux Store State data in the subcomponent (take CustomComponent as a case, and other subcomponents are the same)

    //CustomComponent.js
    import React,{Component} from 'react';
    import {View,Text,Button} from 'react-native';
    
    //Import Action. Click the following business to trigger dispatch(action)
    import * as Actions from '../actions/Actions';
    
    //1. Import connect. You need to connect the current component and Redux Store through the connect.
    import {connect} from 'react-redux';
    
    export default class CustomComponent extends Component{
      render(){
        return(
           <View style={{flex: 1, flexDirection: 'column', alignItems: 'center'}}>
                 /**
                  *5.The current component is obtained by specifying the custom property in the mapStateToProps function through this.props.xxx
                  *	 Value mapped from Redux Store State.
                  */
                 <Text style={{marginTop: 20}}>{this.props.showText}</Text>
    
                 <Button title={'Simulated landing'} onPress={() => {
                     /**
                      *6.The current component calls the mapDispatchToProps customized function through this.props.xxx(),
                      *In this way, the store.dispatch(action) is indirectly triggered to send the action to update the State in the Store
                      */
                     //When the second parameter of connect is not passed, Redux Store will automatically map the dispatch to Props.
                     //this.props.dispatch(Actions.loginWaiting);} 
                     this.props.btnOnClick(1);}
                 }/>
                 <Button title={'Simulated Login succeeded'} onPress={() => {
                     this.props.btnOnClick(1);}
                 }/>
                 <Button title={'Simulated login failed'} onPress={() => {
                     this.props.btnOnClick(2);}
                 }/>
             </View>
        )
      }
    }
    
    /**
     *2.Define mapStateToProps function (when the State in the Store changes, it will call back to change the function)
     *	Returns an Object. Internally, the value in state is mapped to a custom attribute so that the current component can use this.props.xxx
     *	Get data in State.
     */
    const mapStateToProps = (state)=>{
      	return{
            //Map loginText in Redux Store State to the custom showText property.
          	showText:state.loginText,
            //Map the loginStatus in the Redux Store State to the custom showStatus property.
          	showStatus:state.loginStatus,
        }
    }
        
    /**
     *3.Define mapDispatchToProps function:
     *	Returns an Object, the name of the internally defined property function, so that the current component can call this.props.xxx()
     *	To indirectly trigger store.dispatch(action).
     */
    const mapDispatchToProps = (dispatch)=>{
      return {
        changeShowText:(type)=>{
          switch(type){
            case 0:
              dispatch(Actions.loginWaiting);
              break;
            case 1:
              dispatch(Actions.loginSuccess);
              break;
            case 2:
               dispatch(Actions.loginFailed);
              break;   
           }
        }
      }
    }
    
    /**
     *4.Connect the current CustomComponent component with the Redux Store through connect, and use mapStateToProps
     *	mapDispatchToProps Function to map the Redux Store State to the current component Props.
     */
    export default connect(mapStateToProps,mapDispatchToProps)(CustomComponent);
    

Extension:

1. Accessing Redux Store State in nested components

The following components:

Root component APP.js

return(
  <Provider>
    <CustomComponent/>
  </Provider>
)

Subcomponent CustomComponent.js

//Internal introduction of Child components
return(
  ...
  <Child/>
)

If we want to access the Redux Store State in the Child component, the access mode is the same as that of the CustomComponent component, as follows:

Child.js

import React,{Component} from 'react';
import {View, Text, Button} from 'react-native';
//1. Import connect
import {connect} from 'react-redux';
import * as Actions from '../actions/CommonAction';
export default class Child extends Component{
  render(){
    return(
      <View>
        /**
         *Use: specify the property name defined by mapStateToProps through this.props.xxxx
         *Gets the data for the Store State map.
         */
      	<Text>{this.props.xxxx}</Text>
        <Button onPress={()=>{
           /**
            *Call the function declared by mapStateToProps through this.props.xxxx()
            *Indirectly trigger store.dispatch(action) to update Redux Store State.
            */
           this.props.xxx();
        }}>
      </View>
    );
  }
  
//2. Define mapStateToProps function
const mapStateToProps = (state)=>{
    return{
      //TODO...
    }
}

//3. Define mapDispatchToProps function
const mapStateToProps = (dispatch)=>{
    return{
      //TODO...
    }
}

/**
 *4.Connect the current component with Redux Store through connect, and use mapStateToProps and mapStateToProps functions
 *Map Store State and dispatch to Props
 */
export default connect(mapStateToProps,mapStateToProps)(Child);

}
2. Use combinedreducers to merge multiple scattered reducers

In the above code, our actions and reducers are all defined in one file. It is difficult to troubleshoot and maintain errors in the later stage of large and medium-sized projects. Therefore, we reconstruct the project, split the actions and reducers according to the business functions to make them independent, and merge multiple reducers with the help of combinedreducers.

For example, we have landing and registration pages, so we split the original Action into LoginAction and RegisterAction, and split the original Reducer into LoginReducer and RegisterReducer to perform their respective duties and handle related businesses.

  1. Split Action

    LoginAction.js

    /**
     *Landing Action
     *PS:At present, the action trigger carries static data, and the internal data is written,
     *Later, it will expand the interface request to return data and fill it into data
     */
    import {LOGIN_TYPE} from './ActionType';
    export const loginWaiting = {
        type: LOGIN_TYPE.LOGIN_WAITING,
        data: {
            status: 10,
            text: 'Landing...',
        },
    };
    export const loginSuccess = {
        type: LOGIN_TYPE.LOGIN_SUCCESS,
        data: {
            status: 11,
            text: 'Landing successfully!',
        },
    };
    export const loginFailed = {
        type: LOGIN_TYPE.LOGIN_FAILED,
        data: {
            status: 12,
            text: 'Landing failed!',
        },
    };
    

    RegisterAction.js

    /**
     *Registered Action
     */
    import {REGISTER_TYPE} from './ActionType';
    export const registerWaiting = {
        type: REGISTER_TYPE.REGISTER_WAITING,
        data: {
            status: 20,
            text: 'In registration...',
        },
    };
    export const registerSuccess = {
        type: REGISTER_TYPE.REGISTER_SUCCESS,
        data: {
            status: 21,
            text: 'login was successful!',
        },
    };
    export const registerFailed = {
        type: REGISTER_TYPE.REGISTER_FAILED,
        data: {
            status: 22,
            text: 'login has failed!',
        },
    };
    
  2. Split Reducer

    LoginReducer.js

    //Default landing page properties
    const defaultLoginState = {
        Ui: {
            loginStatus: '',   //Login status (used to control whether the login button can be clicked, and display the loading box, etc.)
            loginText: '',     //The text of prompt in different login States
        },
    };
    const LoginReducer = (state = defaultLoginState, action) => {
        switch (action.type) {
            case LOGIN_TYPE.LOGIN_WAITING:
                return {
                    ...state,
                    Ui: {
                        loginStatus: action.data.status,
                        loginText: action.data.text,
                    },
                };
            case LOGIN_TYPE.LOGIN_SUCCESS:
                return {
                    ...state,
                    Ui: {
                        loginStatus: action.data.status,
                        loginText: action.data.text,
                    },
                };
            case LOGIN_TYPE.LOGIN_FAILED:
                return {
                    ...state,
                    Ui: {
                        loginStatus: action.data.status,
                        loginText: action.data.text,
                    },
                };
            default:
                return state;
        }
    };
    export default LoginReducer;
    

    RegisterReducer.js

     //Registration page default status
    const defaultRegisterState = {
        Ui: {
            registerStatus: '',   //Login status (used to control whether the login button can be clicked, and display the loading box, etc.)
            registerText: '',     //The text of prompt in different login States
        },
    };
    const RegisterReducer = (state = defaultRegisterState, action) => {
        switch (action.type) {
            case REGISTER_TYPE.REGISTER_WAITING:
                return {
                    ...state,
                    Ui: {
                        registerStatus: action.data.status,
                        registerText: action.data.text,
                    },
                };
            case REGISTER_TYPE.REGISTER_SUCCESS:
                return {
                    ...state,
                    Ui: {
                        registerStatus: action.data.status,
                        registerText: action.data.text,
                    },
                };
            case REGISTER_TYPE.REGISTER_FAILED:
                return {
                    ...state,
                    Ui: {
                        registerStatus: action.data.status,
                        registerText: action.data.text,
                    },
                };
            default:
                return state;
        }
    };
    export default RegisterReducer;
    

    Combine the reducers through the combinedreducer ({key1: reducer1, key2: reducer2})

    Be careful:

    • When using combinedreducers to combine reducers, we can specify the Reducer name key or omit it (the component name exported by Reducer is used by default).
    • After combinedreducers are combined, when mapping through mapStateToProps function in the presentation component, we need to specify the Reducer name specified during combinedreducers merging to access the data in the Redux Store State (see the following case)
    //Merge Reducer
    import LoginReducer from './LoginReducer';
    import RegisterReducer from './RegisterReducer';
    const AppReducers = combineReducers({
        LoginReducer,                   //LoginReducer name is not specified. LoginReducer is used by default
        registerReducer:RegisterReducer,//Specify LoginReducer as registerReducer,
    });
    export default AppReducers;
    

    The use of subsequent reducers remains the same, and the Store is created by specifying AppReducers to use createStore.

    After reconstruction, run it to see the data format in our State as follows:

    /**
     *Through the combinedreducers, in the Redux Store State
     *Automatically add names to different reducers to distinguish their data status areas
     */
    {
    	"LoginReducer": {
    		"Ui": {
    			"loginStatus": 10,
    			"loginText": "Landing..."
    		}
    	},
    	"registerReducer": {
    		"Ui": {
    			"registerStatus": "",
    			"registerText": ""
    		}
    	}
    }
    

    Next, we map the Redux Store State to the Props of the display component in mapStateToProps

    Landing page / component (Login.js)

    //Define the first parameter of the connect function: map the state in the Store to the props of the current component
    const mapStateToProps = (state) => {return {
            /**
             *Here, we map the properties in the query Redux Store State to the Props properties of the current component through state.xxx.
             *Where [xxx] is the name specified when combinedreducers merges reducers (no default component export name is specified)
             */
            loginShowText: state.LoginReducer.Ui.loginText,
            loginShowStatus: state.LoginReducer.Ui.loginStatus,
        };
    };
    

    Registration page / component (Register.js)

    //Define the first parameter of the connect function: map the state in the Store to the props of the current component
    const mapStateToProps = (state) => {
        return {
            /**
             *Use state.xxxx to specify the name specified when combinedreducers merge reducers,
             *Map the Redux Store State property to the property of the presentation component.
             */
            registerShowText: state.registerReducer.Ui.registerText,
            registerShowStatus: state.registerReducer.Ui.registerStatus,
        };
    };
    

    The restructured project structure is as follows, so we can deal with Action and business logic in Reducer according to the business module to make the logic clearer and improve the readability and maintainability of the project.

3. Use bindActionCreators to simplify the distribution of actions

What is bindActionCreators?

The function of bindActionCreators is to convert single or multiple action creators into the function set form of dispatch(action) in the connectmapDispatchToProps function that uses the connect of redux to associate react with redux store. Instead of manually dispatching (actioncreator (type)), developers can call methods directly.

bindActionCreators principle

bindActionCreators is actually a function that encapsulates the part of the dispatch that is directly combined with a single or multiple action creator s and then sent out. bindActionCreators will send this function using dispatch.

Use vs. do not use bindActionCreators

If we create an action through action creator

UserAction.js

//Add user's synchronization action
export const addUser = (user) => {
    return {
        type: ADD_USER,
        user,
    };
};
//Delete user's synchronization action
export const removeUser = (user)=>{
    return {
        type: REMOVE_USER,
        user,
    };
}
//Calculate total users
export const sumUser = () => {
    return {
        type: SUM_USER,
    };
};

Then use in TestComponent.js through mapDispatchToProps function:

  • Do not use bindActionCreators

    //1. Import UserAction
    import {addUser,removeUser,sumUser} from './actions/UserAction';
    
    //2. Define mapDispatchToProps function of connect
    const mapDispatchToProps = (dispatch)=>{
      return{
        propsAddUser:(user)=>{
          //Distribute the specified Action through dispatch
          dispatch(addUser(user))
        },
        propsRemoveUser:(user)=>{
          dispatch(removeUser(user))
        },
        propsSumUser:()=>{
          dispatch(sumUser())
        }
      }
    }
    //connect to associate react with Redux Store and export components
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)
    
    //3. Call the mapped props defined in mapDispatchToProps in the component
    <Button title='Add user' onpress={()=>{
         let user={
           name:'zcmain',
           age:20,
           address:'Shanghai China'
         }
         //Just call the properties defined in mapDispatchToProps through this.props.xxx.
         this.props.propsAaddUser(user);
         //this.props.propsRemoveUser(user);
         //this.props.propsSumUser();
    }}>
    
  • Using bindActoinCreators

    Format:

    bindActionCreators(actionCreators,dispatch)

    Parameters:

    • actionCreators: (function or object): it can also be an object. All elements of this object are action create functions.
    • Dispatch: (function): the function available on the Store instance dispatch.

    Example:

    //1. Import UserAction
    import {addUser,removeUser,sumUser} from './actions/UserAction';
    
    //2. Import bindActionCreators in redux
    import {bindActionCreators} from 'redux';
    
    //3. Define mapDispatchToProps function of connect
    const mapDispatchToProps = (dispatch)=>{
      return{
        /**
         *Use bindActionCreators to convert multiple action creator s into dispatch(action) form,
         *disptch(action) is not called manually here.
         */
         actions:bindActionCreators({
           propsAddUser:(user)=>addUser(user),
           propsRemoveUser:(user)=>removeUser(user),
           propsSumUser:sumUser,  //action creator without parameters
         },dispatch),
      }
    }
    //connect to associate react with Redux Store and export components
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)
    
    //4. Call the mapped props defined in mapDispatchToProps in the component
    <Button title='Add user' onpress={()=>{
         let user={
           name:'zcmain',
           age:20,
           address:'Shanghai China'
         }
         /**
          *Specify calling mapDispatchToProps through this.props.actions.xxxx
          *bindActionCreators Defined props.
          */
         this.props.actions.propsAddUser(user);
         //this.props.actions.propsRemoveUser(user);
         //this.props.actions.propsSumUser();
    }}>
    

    **(recommended) import * as xxx to import all action creator s in a file

    //1. Import all action creator s in UserAction
    import * as UserActions from './actions/UserAction';
    
    //2. Import bindActionCreators in redux
    import {bindActionCreators} from 'redux';
    
    //3. Define mapDispatchToProps function of connect
    const mapDispatchToProps = (dispatch)=>{
      return {
        /**
         *Convert all action creator s in UserAction.js to dispatch(action) through bindActionCreators
         *Form, the disptch(action) is not called manually here.
         */
        actions:bindActionCreators(UserActions,dispatch);
      }
    }
    //Associate react with Redux Store through connect
    export default connect(mapStateToProps,mapDispatchToProps)(TestComponent)
    
    //4. Call the mapped props defined in mapDispatchToProps in the component
    <Button title='Add user' onpress={()=>{
         let user={
           name:'zcmain',
           age:20,
           address:'Shanghai China'
         }
         //Specify to call the specific action in UserAction.js through this.props.actions.xxxx.
         this.props.actions.addUser(user);
         //this.props.actions.removeUser(user);
         //this.props.actions.sumUser();
    }}>
    

How to use bindActionCreator for asynchronous Action

When we use Redux thunk to implement asynchronous Action by creating return functions, how to use asynchronous Action in bindActionCreators? In fact, it is not very different from synchronous Action. If the function called by asynchronous Action has a return value, and after binding the secondary asynchronous function through bindActionCreator, we will directly accept the return value when calling the asynchronous function through this.props.xxxx (xxx is the function name).

Asynchronous Action (UserAction.js)

//Create Action by creating a return function
const addUser = (user)=>{
  return (dispatch)=>{
     //Asynchronous Action has return value
     return await new Promise((resolve, reject) => {
            //Simulate asynchronous network requests
            setTimeout(() => {
                dispatch(changeLoginBtnEnable(true));
                let userInfo = {
                        userId: 10001,
                        realName: 'zcmain',
                        address: 'Shanghai China',
                    };
                 dispatch(updateUserInfoVo(userInfo));
                 resolve('success');
                }, 2000);
            });
       }
 }

Bindactioncreators to bind asynchronous actions

...
import {UserActions} from './actions/UserAction';

const mapDispatchToProps = (dispatch)=>{
  return{
    //Convert action creator to dispatch(action) through bindActionCreatros
    actions:bindActionCreatros(UserActions,dispatch);
  }
}

Use asynchronous Action in component

...
<Button title='Add user',onPress={()=>{
    let user={
      id:'1',
      name:'zcmain',
    }
    /**
     *If the call function of the action creator bound through bindActionCreator has a return value,
     *Directly accept the return when calling through this.props
     */
    this.props.actions.addUser(user)
      .then((response)=>{
           //TOOD...
      		 console.log('Success:' + JSON.stringify(response);
    	},(error)=>{
      		 console.log('Failure:' + error.message);
    	}).catch((exception)=>{
      		 console.log('Exception:' + JSON.stringify(exception));
    });
}}/>

Source code analysis of bindActionCreators

  1. Determine whether the parameter passed in is an object. If it is a function, it will directly return a function that wraps the dispatch;
  2. If it is an object, generate the package dispatch function according to the corresponding key;
/**
 *bindActionCreators function
 *Parameter Description:
 *@param actionCreators: action create Function, can be a single function, can also be an object, all elements of this object
 *They are all action create functions;
 *@param dispatch: store.dispatch Method;
 */
export default function bindActionCreators(actionCreators, dispatch) {
  /**
   *If actionCreators is a function, the bindActionCreator method is called to the action create function
   *Bind with dispatch.
   */
  if (typeof actionCreators === 'function') {
      return bindActionCreator(actionCreators, dispatch)
  }
  /**
   *If actionCreators is not an object or actionCreators is empty, an error is reported
   */
  if (typeof actionCreators !== 'object' || actionCreators === null) {
      throw new Error('bindActionCreators expected an object or a function, + 
                      'instead received ${actionCreators === null ?+
                      'null' : typeof actionCreators}.' +
                      'Did you write "import ActionCreators from" instead of +
                      '"import * as ActionCreators from"?')
  }

  //Otherwise, actionCreators is an object that gets the names of all action create functions
  const keys = Object.keys(actionCreators)
  //Traverse the actionCreators array object to save the collection after the dispatch and action create functions are bound
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    // Exclusion value is not action create of function
    if (typeof actionCreator === 'function') {
      // Binding
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  //Return the bound object
 return boundActionCreators
}

/**
 *bindActionCreator function
 */
function bindActionCreator(actionCreator, dispatch) {
  // The main function of this function is to return a function. When we call the returned function, we will automatically dispatch the corresponding action
  // In fact, this one can be changed to the following form
  // return function(...args) {return dispatch(actionCreator.apply(this, args))}
  return function() { return dispatch(actionCreator.apply(this, arguments)) }
}

senior

Asynchronous Action

The actions we created in the previous section are all synchronous. When we dispatch(action), the state will be updated immediately.

Create a synchronization action (an action object is returned):

//Create synchronization action
export const syncAddItem = {
  type:'addItem',
  text:'Add a piece of data',
}
//Or it can be created by function (it can receive parameters and return action)
export const syncAddItem=(desc)=>{
  return{
    type:'addItem',
    text:desc,
  }
}

//The State will be updated immediately after the synchronization action is triggered through the store
store.dispatch(syncAddItem);
store.dispatch(syncAddItem('Add a piece of data'));

For asynchronous action creation, we need to use the** Redux Thunk **Middleware. The action creation function can return functions in addition to the action object. This action creation function becomes thunk.

What is (why) Redux Thunk?

We need to use middleware such as Redux thunk because Redux storage only supports synchronous data flow. So middleware came to rescue! Middleware allows asynchronous data flow, explains any content you assign, and finally returns a common object that allows synchronous Redux data flow to continue. Therefore, Redux middleware can solve many key asynchronous requirements (such as axios requests).

Redux Thunk middleware allows you to write return functions instead of returning action objects. Thunk middleware can be used to dispatch delayed actions, or only when certain conditions are met. The internal function takes the dispatch and getState of the store as parameters.

When action creates a function return function, this function will be executed by Redux Thunk middleWare (the return (dispatch) function returned by the asynchronous action created below will be executed by Thunk Middleware). This function does not need to be kept pure; it can have side effects, including executing asynchronous API requests. This function can also execute dispatch(action), just like the action of dispatch synchronization.

Redux Thunk used in asynchronous Action

1. Create an asynchronous action (a function returned will be called by the Thunk Middleware):

//Create an asynchronous action,
export const asyncAction1 = (str) => {
    //Returns a function that receives the dispatch parameter (this function will be called by the Thunk Middleware),
    return (dispatch) => {
        //Specify other operations after 2 seconds, such as triggering dispatch(action) to update State
        setTimeout(() => {
            //dispatch(action);
            console.log(str);
        }, 2000);
    };
};

//The storet distributes asynchronous actions through the dispatch method
store.dispatch(asyncAction1('asynchronous Action Create function'))

Of course, in addition to receiving the dispatch parameter, the asynchronous Action return function can also accept the getState parameter. According to the state in the getState, we can make logical judgment and execute different dispatch:

//Create an asynchronous action,
export const asyncAction1 = (str) => {
    //Returns a function that receives the dispatch and getState parameters (this function will be called by the Thunk Middleware),
    return (dispatch,getState) => {
      //Get the counter property in the State through getState, and if it is even, return not to trigger dispatch
       const {counter} = getState();
       if(counter % 2 === 0){
          return;
       }
       //Otherwise, specify other operations after 2 seconds, such as triggering dispatch(action) to update State
       setTimeout(() => {
            //dispatch(action);
            console.log(str);
        }, 2000);
    };
};

//store calls the dispatch method
store.dispatch(asyncAction1('asynchronous Action Create function receive dispatch and getState attribute'))

In addition to receiving dispatch and getState parameters, the asynchronous Action return function can also use Redux Thunk to inject custom parameters with extraargument function:

//Inject custom parameters into asynchronous Action return function through withExtraArgument of Redux Thunk
import {createStore,applyMiddleWare} from 'redux';
import thunk from 'redux-thunk';

//Single parameter injection
const name ='zcmain';

//Multiple parameters are wrapped into object injection
const age = 20,
const city = 'ShanHai',
const userInfo = {
  name,
  age
  city,
}

const store = createStore(
  reducer,
  //When creating the Store, the user-defined parameters are injected into the asynchronous Action return function through thunk.withExtraArgument
  applyMiddleware(thunk.withExtraArgument(name,userInfo)),
);
//Asynchronous Action
const asyncAction1 = ()=>{
  /**
   *The return function takes three parameters, where name and userInfo pass the
   *Thunk.withExtraArgument(name,userInfo)Custom parameters for injection.
   */
  return (dispatch,getState,name,userInfo)=>{
       //TODO...you can use name and userInfo here
  }
}

The function called by the Thunk middleware middleware can have a return value, which is passed as the return value of the dispatch method.

//Create an asynchronous Action
export const asyncAction2 = (url) => {
    //Returns a function that receives the dispatch parameter
    return (dispatch) => {
        /**
         *thunk middleWare The function called returns a Promise object, which is passed as the return value of the dispatch method,
         *Here, we call dispatch(action) to update State through Fetch network request and response result.
         *Be careful:
         *  Don't use catch because it will catch any errors in the dispatch and rendering,
         *  Cause 'Unexpected batch number' error.
         *  https://github.com/facebook/react/issues/6895
         */
        return fetch(url)
          .then((response) =>{
                 response.json()
               },(error)=>{
          
           })
          .then((json) => {
            //Send dispatch(action) to update State after receiving the corresponding
            dispatch({type:'UPDATA',data:json});
            return json;
        });
    };
};

/**
 *store The asynchronous action is triggered through the dispatch method because the action return function has a return value,
 *Is passed as the return value of the dispatch method.
 */
store.dispatch(asyncAction2('http://xxx.xxx.xxx.xxx:xxx/test/json))
     .then((json)=>{
      //TODO...
     }).catch(()=>{
      //TODO...
});

Be careful:

Do not use catch in the function with return value executed by Thunk middleWare, because it will catch any error in dispatch and rendering, Unexpected batch number error.

https://github.com/facebook/react/issues/6895

2. Use asynchronous Action

Above, we have created an asynchronous Action. Next, we need to use the asynchronous Action through the Redux Thunk middleware

  1. To install the Redux thunk Library:

    npm -i --save redux-thunk
    #perhaps
    yarn add redux-thunk
    
  2. Use Redux Thunk to create a store through the applyMiddleWare of Redux:

    AppStore.js

    //Introducing createStore and applyMiddleWare
    import {createStore,applyMiddleWare} from 'redux';
    //Introducing thunk Middleware
    import thunk from 'redux-thunk';
    //Introduction of reducers
    import reducers from '../reducers/AppReducers';
    
    //Specify the reducer and middleware to create the store
    const store = createStore(reducers,applyMiddleWare(thunk));
    
    //Export store
    export default store;
    
  3. Use of other components

    LoginComponent.js

    /**
     *In the mapDispatchToProps function of the login page, the asynchronous action is triggered by the dispatch method, and the log will be printed in two seconds;
     *Note: mapDispatchToProps function depends on the Provider and connect of react redux.
     */
    const mapDispatchToProps = (dispatch) => {
        return {
            onclick: () => {
                dispatch(LoginAction.asyncAction1('Sending asynchrony action'));
            },
        };
    };
    
    //It will print in two seconds
    LOG  str:Sending asynchrony Actoin La...
    
    /**
     *dispatch Distribute asynchronous asyncAction2 with return value
     */
    const mapDispatchToProps = (dispatch) => {
        return {
            onclick: () => {
                dispatch(LoginAction.asyncAction2('Sending asynchrony action Accept return value'))
                 .then((json)=>{
                     console.log('dispatch Distribute asynchronously Action And receive the return value:' + json)
                 });
            },
        };
    };
    

Normalized data

Why do you want to set up a normalized state data structure?

In fact, most of the data processed by programs are nested or interrelated. How to use nested and repeated data (object reuse) in state? For example:

const blogPosts = [
    {
        id : "post1",
        author : {username : "user1", name : "User 1"},
        body : "......",
        comments : [
            {
                id : "comment1",
                author : {username : "user2", name : "User 2"},
                comment : ".....",
            },
            {
                id : "comment2",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    },
    {
        id : "post2",
        author : {username : "user2", name : "User 2"},
        body : "......",
        comments : [
            {
                id : "comment3",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            },
            {
                id : "comment4",
                author : {username : "user1", name : "User 1"},
                comment : ".....",
            },
            {
                id : "comment5",
                author : {username : "user3", name : "User 3"},
                comment : ".....",
            }
        ]    
    }
    // and repeat many times
]

The above data structure is relatively complex, and some of the data is repeated. There are also some concerns:

  • It is difficult to ensure that all the reused data are updated at the same time: when the data needs to be updated after redundancy, it is difficult to ensure that all the data are updated.
  • High nesting complexity: nested data means that the reducer logic has more nesting and higher complexity. Especially when you plan to update deep nested data.
  • The immutable data needs to be copied and updated by the ancestor data of the state tree when updating, and the new object reference will cause all UI components connect ed to it to render repeatedly. Although the data to be displayed has not changed in any way, updating the deeply nested data objects will force the completely unrelated UI components to render repeatedly.

Because of this, the recommended way to manage relational data or nested data in Redux Store is to treat this part as a database and store the data in a normal way.

Design normalized State data structure

The normal structure includes the following aspects:

  1. Any type of data has its own "table" in state;
  2. Any "data table" should store each item in an object, where the ID of each item is the key and the item itself is the value.
  3. Any reference to a single project should be based on the ID of the storage project.
  4. The ID array should be used for sorting.

After the state structure in the above blog example is normalized, it may be as follows:

Extract the author and comments objects and use byId

{
    posts : {
        byId : {
            "post1" : {
                id : "post1",
                author : "user1",
                body : "......",
                comments : ["comment1", "comment2"]    
            },
            "post2" : {
                id : "post2",
                author : "user2",
                body : "......",
                comments : ["comment3", "comment4", "comment5"]    
            }
        }
        allIds : ["post1", "post2"]
    },
    comments : {
        byId : {
            "comment1" : {
                id : "comment1",
                author : "user2",
                comment : ".....",
            },
            "comment2" : {
                id : "comment2",
                author : "user3",
                comment : ".....",
            },
            "comment3" : {
                id : "comment3",
                author : "user3",
                comment : ".....",
            },
            "comment4" : {
                id : "comment4",
                author : "user1",
                comment : ".....",
            },
            "comment5" : {
                id : "comment5",
                author : "user3",
                comment : ".....",
            },
        },
        allIds : ["comment1", "comment2", "comment3", "commment4", "comment5"]
    },
    users : {
        byId : {
            "user1" : {
                username : "user1",
                name : "User 1",
            }
            "user2" : {
                username : "user2",
                name : "User 2",
            }
            "user3" : {
                username : "user3",
                name : "User 3",
            }
        },
        allIds : ["user1", "user2", "user3"]
    }
}

Inter table relationship

Because we regard Redux Store as a database, it is also applicable in many database design rules. For example, for many to many relationships, an intermediate table can be designed to store associated item IDS (often referred to as related tables or associated tables). For consistency, we will also use the same byId and allIds in the actual data item table.

entities:{
  authors:{byId:{},allIds:[]},
  book:{byId:{},allIds:[]},
  authorBook:{
    byId:{
      1:{
        id : 1,
        authorId : 5,
        bookId : 22
      },
      2:{
        id : 2,
        authorId : 5,
        bookId : 15,
      }
    },
    allIds:[1,2]
  }  
}

Nested data normalization

Because API s often send return data in a nested form, the data needs to be converted to a normalized form before it is introduced into the state tree. Normalizr Libraries can help you do this. You can define the type and relationship of the schema and provide the schema and response data to Normalizr, who will output the normal transformation of the response data. The output can be placed in the action for updating the store. For more details on its usage, see the Normalizr documentation.

Manage normalized data

When the data has ID, nesting or association relationship, it should be stored in a normal form: the object can only be stored once, the ID is the key value, and the objects refer to each other through the ID.

Compare Store to database, each item is a separate "table". normalizr,redux-orm This kind of library can provide reference and abstraction in the management of standardized data.

Middleware usage

redux-thunk

Redux Thunk is a middleware that provides asynchronous Action processing in redux. Please refer to the above article for specific use< What is Redux Thunk>

redux-saga

Redux saga is also used to solve the problem of asynchronous interaction in RN, which is consistent with the goal of Redux thunk. The differences are as follows:

  • redux-thunk:

    • Introduction: redux launched a MiddleWare, which is easy to use. It allows action creation functions to return functions in addition to action objects, and the return function can accept dispatch and getState as parameters. This function does not need to be pure; it can also have side effects, including executing asynchronous API requests. This function can also dispatch action.
    • Advantages: small code, easy to use, suitable for small and light applications.
    • Disadvantages: the return function is complex and hard to maintain. Because thunk makes the action creation function return not an action object, but a function, and the internal function can be varied, even more complex, which obviously makes the action not easy to maintain.
  • redux-saga

    • Introduction: the description on the official website Redux saga is a library used to manage the application Side Effect (side effects, such as asynchronous data acquisition, browser cache access, etc.), its goal is to make Side Effect management easier, execution more efficient, and test more simple.
    • Advantages: avoid callback hell (currently, thunk can also be solved by using async/await), convenient for testing and maintenance, and suitable for large applications.
    • Disadvantages: steep learning route, large amount of template code.

    In many normal and small to medium applications, use async / await style Redux thunk. It saves you a lot of boilerplate code / Operations / types, and you don't need to switch between many different sagas.ts, or maintain a specific sagas tree. However, if you are developing a large application that contains very complex asynchrony and requires some features, such as concurrent / parallel mode, or high requirements for testing and maintenance (especially in Test Driven Development), then Redux sagas may save your life.

    See also Redux-Thunk vs. Redux-Saga>

redux-ignore

Redux ignore can specify the trigger condition of the reducer function (for example, specify a / some actions to trigger the current reducer function).

For reducers merged and split by combinedreducers, all reducers will be called when each action is triggered. JavaScript engine has enough ability to run a large number of function calls per second, and most of the sub reducers only use switch statements, and the default state will be returned for most of the actions. If you are still concerned about the performance of the reducer, you can use a similar Redux ignore tool to ensure that only some actions will call one or several reducers.

  • Install Redux ignore

    npm -i --save redux-ignore
    #perhaps
    yarn add redux-ignore
    
  • Configure the combinedreducer to combine the reducers, and specify the actions that can trigger the implementation of the reducers through the filteractions (or ignore the specified actions through ignoreActions)

    import {combineReducers} from 'redux';
    import {filterActions} from 'redux-ignore/src';
    import {reducerA} from './ReducerA';
    import {reducerB} from './ReducerB';
    
    //Combine the decentralized reducers into one Reducer through combinedreducers
    const reducers = combineReducers(
        reducerA:fileterAction(
      							reducerA,
                    /**
                     *Specifies the action array that can trigger the execution of reducerA, only the actions in the array are dispatch ed,
                     *reducerA Will be called.
                     */
      							[
      								'actionA1',       
      								'actionA2'...
      							]),
      
        reducerB:fileterAction(
          					reducerB,
          				 /**
                     *Specifies the action array that can trigger the execution of reducerB. Only the actions in the array are dispatch ed,
                     *reducerA Will be called.
                     */
          					[
                      'actionB1',
                      'actionB2'...
                    ]);
    )
    

redux-router

redux-promise

reselect

Why reselect?

When using react Redux, the mapStateToProps function receives the state of the entire Redux store as props, and then returns an object passed in to the component props. This function is called a selector selector.

Let's look at a mapStateToProps function

// mapStateToProps is a selector, which is called every time a component is updated
// [disadvantages] the visibleTodos will be recalculated every time the component is updated. If the calculation amount is large, it will cause performance problems
const mapStateToProps = (state) => ({
    visibleTodos: selectTodos(state.todos, state.visibilityFilter),
    visibilityFilter: state.visibilityFilter
});

// A state evaluation function
export const selectTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(todo => todo.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(todo => !todo.completed)
  }
}

Before the connect function was implemented, we knew that the function mapping props was stored. Subscribe(). Therefore, whenever the component is updated, mapStateToProps will be called regardless of whether the state is changed, and mapStateToProps will call the state calculation function selectTodos when calculating the state. The procedure is as follows:

store.subscribe() (registration event) - > call mapStateToProps (a selector, return state) when the state is updated) - > call the state calculation function selectTodos

Then, the problem comes. If the calculation amount of the slicer is large, the recalculation of each update will cause performance problems.

The starting point to solve the performance problem is to avoid unnecessary calculation.

The way to solve the problem: start with selector, mapStateToProps. If the state parameters accepted by selector remain unchanged, the calculation function will not be called, and the previous results will be directly used.

What is reselect

reselect is actually a middleware of redux. It obtains a new state through the incoming multiple state calculations, and then passes it to the Redux Store. Its main purpose is to perform the intermediate calculation, so that the calculation state is cached, so as to determine whether the calculation function (selectTodos) needs to be called according to the incoming state, instead of being called every time the component is updated, so it is more efficient.

Reference

64 original articles published, 40 praised, 350000 visitors+
Private letter follow

Tags: React JSON Attribute Javascript

Posted on Wed, 15 Jan 2020 23:45:15 -0500 by Canman2005