As our needs continue to rise, we need more complex data delivery, more levels of data exchange. So why don’t we just give all the data to a hub, independent of all the components, and the hub distributes the data, so that whatever component needs data, we can easily distribute it to him. There is a library that can help us do this: Redux, which enables centralized state management

When is Redux used?

First, let’s clarify the role of Redux for centralized state management.

Redux is suitable for multi-interaction, multi-data source scenarios. Simple to understand is complex

From a component perspective, we can try Redux when we have the following application scenarios

  1. When a component’s state needs to be shared
  2. When a component needs to change the state of another component
  3. When a component needs to change the global state

In addition, there are many cases that need to be implemented using Redux (not learning hook, maybe there is a better way).

This diagram illustrates the difference between pure React and Redux

Redux workflow

The component sends an action object to the Store by calling the store.dispatch method. When the Store receives the action object, The previous state and incoming actions are sent to the reducer together. After receiving the data, the reducer changes the data and returns a new state to the Store. Finally, the Store changes the state

(Photo from gold Digger Community, deleted)

Redux has three core concepts

1. store

Store is the core of Redux, which is the data center of Redux. We can store any data we want in the Store, and when we need to use the data, we can pull it out of the store. So we need to create a store first, and in Redux we can use the createStore API to create a store

In production, we need to add a store.js file to the redux folder in the SRC directory. In this file, create a Store object and expose it

So we need to expose two methods from Redux

import {
    createStore,
    applyMiddleware
} from 'redux'
Copy the code

And introduce a Reducer for the count component service

import countReducer from './count_reducer'
Copy the code

Finally, the createStore method is called to expose the Store

export default createStore(countReducer, applyMiddleware(thunk))
Copy the code

Middleware is used here and this article should not cover ~

There are several common built-in methods under the Store object

To get the current store, use the getStore method

const state = store.getState();
Copy the code

In our previous flowchart, we need to derive an Action object from the Store via the Dispatch method in the store

Store. Dispatch (` action object `)Copy the code

Finally, we have a subscribe method, which helps us subscribe to store changes, and its callback is executed whenever the store changes

In order to listen for updates, we can bind the SUBSCRIBE method to the component’s mount life cycle function, but this will be troublesome when we have a large number of components, so we can directly use the SUBSCRIBE function to listen for changes in the entire App component

store.subscribe(() => {
    ReactDOM.render( < App /> , document.getElementById('root'))
})
Copy the code

2. action

Actions are the only source of data in the store, and generally we pass actions to the store by calling store.dispatch

The action we need to pass is an object that must have a type value

For example, here we expose a method that returns an Action object

export const createIncrementAction = data => ({
    type: INCREMENT,
    data
})
Copy the code

When we call it, we return an Action object

3. reducer

In Reducer, we need to specify the operation type of the state and what data updates to make, so this type is necessary.

Reducer will perform corresponding operations on the state according to action instructions and return the state after the operation

Here, we judge the type passed in the received action

export default function countReducer(preState = initState, action) {
    const {
        type,
        data
    } = action;
    switch (type) {
        case INCREMENT:
            return preState + data
        case DECREMENT:
            return preState - data
        default:
            return preState
    }
}
Copy the code

Change the data to return to the new state

Create constant file

In our normal coding, it is possible to have spelling errors, but we will find that spelling errors do not necessarily report errors, so it can be difficult to understand.

In the redux directory, we can create a constant file that defines some of the variables we use in our code, for example

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
Copy the code

Write these two words in the constant file and expose them so that when we need to use them, we can import the file and just use its name

Use INCREMENT directly

Implementing asynchronous Actions

To begin with, we call an asynchronous function directly, which is fine, but why not redux?

incrementAsync = () => {
    const { value } = this.selectNumber
    const { count } = this.state;
    setTimeout(() => {
        this.setState({ count: count + value * 1 })
    }, 500);
}
Copy the code

We can start by wrapping it into an Action object to call

Export const createIncrementAsyncAction = (data and time) = > {/ / without the introduction of the store, Return (dispatch) => {setTimeout(() => {dispatch(createIncrementAction(data))}, time)}}Copy the code

When we click the asynchronous add operation, we call this function, which receives a delay plus time and the data required for the action. The only difference is that it returns a timer function

But this is obviously an error, and it needs to receive an object by default

If we need to implement an incoming function, then we need to say: you just need to silently help me execute the following function!

This is where middleware comes in, exposing applyMiddleware to perform functions in native Redux and introducing redux-Thunk middleware (manual download required)

import thunk from 'redux-thunk'
Copy the code

Just pass it along with the second argument

export default createStore(countReducer, applyMiddleware(thunk))
Copy the code

Note: Asynchronous actions do not have to be written. You can wait for the results of an asynchronous task before distributing synchronous actions

Redux’s Three principles

Understanding Redux will help us understand react-Redux

The first principle

One-way data flow: Data flows in one direction throughout Redux

UI components – > action – > store – > reducer – > store

Second principle

State read-only: State changes cannot be controlled in Redux by directly changing state. If you want to change state, you must trigger an action. The reducer was implemented using action

The third principle

Pure function execution: Each Reducer is a pure function without any side effects. The reducer returns a new state, and the state change triggers the SUBSCRIBE in the store

Story synopsis

  • Redux is part of the React Family bucket, which attempts to provide a “predictable state management” mechanism for react applications.

  • Redux stores the entire application state in one place, called a store

  • I’m going to keep a state tree in there

  • Components can dispatch actions to stores instead of notifying other components directly

  • Other components can refresh their views by subscribing to states in the Store

Redux core

1 State

State is a collection of data

Can be understood as the raw materials that factories need to process goods

2 action

If the State changes, the View changes. But the user doesn’t have access to the State, only to the View so the change in State must be caused by the View.

An action is an instruction that changes state. There are as many actions as there are operations on state.

Can you think of an action as an indicator of what happened

3 Reducer machining function

Action puts the state into the Reucer processing function after issuing the command, and returns the new state. You can think of it as a machine for processing

4 store

Store can be understood as a total factory with multiple processing machines

let store = createStore(reducers);
Copy the code

A Store is an object that ties them together. The Store has the following responsibilities:

Maintain application state; Provide the getState() method to getState; Provide a dispatch(action) method to update state; Register a listener with subscribe(listener); Unsubscribe the listener via a function returned by subscribe(listener).Copy the code

We can use store.getState() to find out the state of an item in the factory and use store.dispatch to send an action directive.

A classic case

This is a classic case of Redux

  • Define the Reducer function to change state based on the type of action

  • Actions definition directive

  • Create a store using createStore

  • Call store.dispatch() to issue the command to change state

 import { createStore } from 'redux'
    
    const reducer = (state = {count: 0}, action) => {
      switch (action.type){
        case 'INCREASE': return {count: state.count + 1};
        case 'DECREASE': return {count: state.count - 1};
        default: return state;
      }
    }
    
    const actions = {
      increase: () => ({type: 'INCREASE'}),
      decrease: () => ({type: 'DECREASE'})
    }
    
    const store = createStore(reducer);
    
    store.subscribe(() =>
      console.log(store.getState())
    );
    
    store.dispatch(actions.increase()) // {count: 1}
    store.dispatch(actions.increase()) // {count: 2}
    store.dispatch(actions.increase()) // {count: 3}
Copy the code

We could use store.dispatch directly on the React Component, but that would be inconvenient. We need react-redux in this case

class Todos extends Component {
    render(){
        return(
            <div onCLick={()=>store.dispatch(actions.delTodo()) }>test</div>
        )
    }
}
Copy the code

React-redux

Redux provides the React binding library. It is efficient and flexible.

1 installation

npm install --save react-redux
Copy the code

2 core

  • < Provider store>
  • connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

Any component within a Provider (such as Comp here) that needs to use data from State must be “connected” — the product of wrapping “your component (MyComp)” with connect.

This function allows us to bind the data in the store to the component as props.

The simple process is shown below:

The Connect method in React-Redux wraps getState and Dispatch on the store as props for the component.

Change the code that was dispatched directly on the component to the following:

index.js

import React, { Component } from 'react'; import store from '.. /store'; import actions from '.. /store/actions/list'; import {connect} from 'react-redux'; class Todos extends Component { render(){ return( <div onCLick={()=>this.props.del_todo() }>test</div> ) } } export default connect( state=>state, actions )(Todos);Copy the code

The Provider takes the key store and passes it to each child component

React Redux distinguishes components into container components and UI components

  1. The former handles logic
  2. The latter is only responsible for display and interaction, and does not handle logic internally. The state is completely controlled externally

Two core

  • Provider

    Look at the top component of my code up there four words. Yes, you guessed right. The top component is the Provider. We usually wrap the top component in the Provider component, so that all components can be controlled by react-Redux, but stores must be placed as arguments in the Provider component

    <Provider store = {store}>
        <App />
    <Provider>
    Copy the code
    The purpose of this component is for all components to have access to the data in Redux.Copy the code
  • connect

    This is the hard part of React-Redux, so let’s explain it in detail

    First, remember this line of code:

    connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    Copy the code

    mapStateToProps

    Map state to props (); map data from Redux to props ();

    Here’s an example:

Const mapStateToProps = (state) = > {return {/ / prop: state. | means XXX will be one of the state data is mapped to the props in the foo: state. The bar}}Copy the code

Then you can render using this.props. Foo

class Foo extends Component { constructor(props){ super(props); } render(){return(// this.props. Foo </div>)}} foo = connect()(foo); export default Foo;Copy the code

Then you can finish rendering

mapDispatchToProps

The word translates as props for all kinds of dispatches that you can use directly

Const mapDispatchToProps = (dispatch) => {return {onClick: () => {dispatch({type: 'increatment' }); }}; }Copy the code
class Foo extends Component { constructor(props){ super(props); } render(){return(<button onClick = {this.props. OnClick}> increase</button>)}} Foo = connect()(Foo); export default Foo;Copy the code

You can call dispatch directly with this.props. OnClick, so you don’t need to store

That concludes the basic introduction to React-Redux

How does Connect work?

Connect () takes four parameters, mapStateToProps, mapDispatchToProps, mergeProps, and options.

1 mapStateToProps This function allows us to bind data in a store to components as props.

reducer.js

export default function (state = { lists: [{text:' mobile plan '}],newType:'all'}, action) {switch (action.type) {case types.add_todo: return {... state,lists:[...state.lists,{text:action.text}]} case types.TOGGLE_TODO: return {... state,lists:state.lists.map((item,index)=>{ if(index == action.index){ item.completed = ! item.completed } return item })} case types.DEL_TODO: return {... state,lists:[...state.lists.slice(0,action.index),...state.lists.slice(action.index+1)]} case types.SWITCH_TYPE: console.log({... state,newType:action.newType}) return {... state,newType:action.newType} default: return state; }}Copy the code

In reducer. Js, the initialized state is defined. By using the connect method, we can get the initialized state using this.props. Lists.

import React, { Component } from 'react'; import store from '.. /store'; import actions from '.. /store/actions/list'; import {connect} from 'react-redux'; class Todos extends Component { render(){ return( { + <ul> + this.props.state.lists.map(list =>( + <li>{list.text}</li> + )) + </ul> } <div onCLick={()=>this.props.del_todo() }>test</div> ) } } export default connect( state=>state, actions )(Todos);Copy the code

When state changes, or ownProps changes, mapStateToProps is called, and a new stateProps is computed, which (after merging with ownProps) is updated to MyComp.

2 mapDispatchToProps(dispatch, ownProps): The second parameter to dispatchProps Connect is mapDispatchprops, which binds action as props to MyComp.

action.js

import * as types from ".. /action-types"; export default{ add_todo(text){ return { type: types.ADD_TODO, text: text} }, del_todo(idx){ return {type:types.DEL_TODO, index: idx} }, toggle_todo(index){ return {type:types.TOGGLE_TODO, index} }, del_todo(index){ return {type:types.DEL_TODO, index} }, switch_type(newType){ return {type:types.SWITCH_TYPE, newType} } }Copy the code

The commands I define to change the state in action.js become props bound to the REAC component via connect’s mapDispatchToProps method.

We can easily use to call

    <div onCLick={()=>this.props.del_todo() }>test</div>
Copy the code

in-depth

Given this, we can see that we didn’t use the store.dispatch method to issue the command, but the state has changed and the view has changed, so what happened?

store.dispatch(actions.increase())
Copy the code

The key is connect()

Connect Principle simplified version

import React,{Component} from 'react'; import {bindActionCreators} from 'redux'; import propTypes from 'prop-types'; export default function(mapStateToProps,mapDispatchToProps){ return function(WrapedComponent){ class ProxyComponent extends Component{ static contextTypes = { store:propTypes.object } constructor(props,context){ super(props,context); this.store = context.store; this.state = mapStateToProps(this.store.getState()); } componentWillMount(){ this.unsubscribe = this.store.subscribe(()=>{ this.setState(mapStateToProps(this.store.getState())); }); } componentWillUnmount(){ this.unsubscribe(); } render(){ let actions= {}; if(typeof mapDispatchToProps == 'function'){ actions = mapDispatchToProps(this.store.disaptch); }else if(typeof mapDispatchToProps == 'object'){ console.log('object', mapDispatchToProps) actions = bindActionCreators(mapDispatchToProps,this.store.dispatch); } return <WrapedComponent {... this.state} {... actions}/> } } return ProxyComponent; }}Copy the code

1. Return state from connect by returning state from a store on the Provided parent component

mapStateToProps(this.store.getState());
Copy the code

Redux’s helper function, bindActionCreators(), listens on each action with Dispatch.

 bindActionCreators(mapDispatchToProps,this.store.dispatch);
Copy the code

To call the method on props, the store.dispach (XXX) event is automatically dispach (XXX)

React-redux simple example project link

reference

Redux is transparent at one o ‘clock

Redux tutorial (3) : React-redux