Detailed use process and cases of Redux and react redux. During the day of sharing brick moving knowledge, I dozed off several times. Ah ha ha~

Move bricks, move bricks, share some brick moving skills.
Chapter 6 is almost over. There are still some scattered ones. Move slowly.
Click here for previous chapters~

Chapter 6 - redux

redux Chinese official website

6.1 what is redux

The official saying is this: redux is a predictable state container for JavaScript applications. It can help you write applications that behave consistently, run in different environments (client, server and native) and are easy to test.

Official explanations are sometimes incomprehensible

Tell me what I understand.

JavaScript applications: redux and react are two different things. They just look like each other. redux can be used in any application.

redux has a core thing, store. Now there is a new problem, what is store.

Store English means store. In redux, it is a place to store data, which is equivalent to a container and a warehouse. The whole program has only one store. This facilitates centralized state management.

Why is it predictable?

Because we set up the data modification process managed by redux, that is, we have told him what to do. And the previous status will be received before each modification. The process is predictable and traceable.

6.2 why do I need redux

When the functions of the program are complex, there are many components, and it is difficult to share the state, using redux is a better solution.

We can understand from these two pictures: there are four houses (components) A, B, C and D. they always borrow things from each other. Communicate with each other. (component interaction, state sharing, data modification). In our previous method, like transferring data through A common parent component, the parent component will become very bloated, because it needs to define some states and methods it does not need, just to facilitate the communication between sons.

Or use the message subscription publish mechanism. If B needs some data of A, it needs to subscribe, and then A publishes it. Then D also needs, so D also needs to subscribe. The components come and go like A mess as shown in the figure.

The use of redux is equivalent to building a warehouse. Each house puts the things that need to be shared in the warehouse, and whoever needs it will get it. It not only stores the resources that need to be shared in one place, but also facilitates the use of other components.

Unnecessary, do not use redux

It is not how to program or use react that you need to use redux.

There is a saying like this

"If you don't know if you need Redux, you don't need it."

Why? redux is convenient for components to share state. If there are few component interactions, few states to share, and it can be solved without redux, redux is not needed at all.

Usage scenario

  • Components need to share state and interact more
  • Users with different identities have different usage methods (such as ordinary users and administrators)
  • Multiple users can collaborate
  • Interact with the server a lot, or use WebSocket
  • View wants to get data from multiple sources

6.3 workflow diagram of Redux

Before introducing how to use it, it is necessary to understand the workflow of redux, which is helpful to understand.

Component is the component

ActionCreators redux is divided into three parts. The core part is Store, followed by ActionCreators and Reducers

Where ActionCreators is the to create an action. That is, a module for creating behavior.

Reducers handles the behavior sent by the dispatch. That is, the module that actually takes action.

Store is the core part of saving state. It is used to send behavior, store data, or reflect the results.

We can understand that redux is a hotel. Each component is a guest. The guest orders through the action creators to tell what dishes they need. So the waiter told the boss about the store, and then the boss asked the chef to cook. Finally, the guest took the food from the boss. Of course, this is somewhat different from the actual situation, but it doesn't prevent us from better understanding the process.

Back to reality, when the component needs to issue a behavior, it calls the dispatch method on the store. On the way, it takes the action from ActionCreators and passes it to the store as a parameter of the dispatch. That is, the component actually calls the dispatch method.

Then the store passes the action of the current state to the reducer. The reducer returns the new state to the store after processing. The component then obtains the new state data through the getState() method of the store.

6.4 use of Redux

Installation and foundation preparation

Through npm

npm install redux

Via yarn

yarn add redux

Create related folders and files

Create components, redux folders and related files in redux (as shown in the figure)

Because different components have different behaviors, you can put different files for creating actions in one folder, as shown in the actions folder in the figure.

Similarly, the actions of different components are different, and different reducer s are required, so they are placed in the same folder.

There is only one store, so you can put it directly under the redux folder.

constant.js file is used to store type constants. Because the value of type is given in advance, different operations are triggered by different types. Therefore, the value of type needs to be used in both components and reducer. Using constants is easy to modify and put in wrong words.

Basic use

Suppose we need to complete an example

Store the data of the result in state, and click + or - to add or subtract one from the data. The result displays 99 by default.

action

There is no need to introduce any API to create the action file. We need to create relevant methods, which return through action.

Action is an object, including two attributes, type and data, which are type and data respectively. Type indicates the operation that the reducer should perform at that time, and data is the data passed. This data source component. That is, the method exposed by the action file is called for the component, and the component will pass in a data.

// Expose the corresponding action creation methods. When actions need to be used elsewhere, call this method to create and return actions.
export let createIncrease = (data) => ({ type: "increase", data })   
export let createDecrease = (data) => ({ type: "decrease", data })
reducer

The reducer file also does not need to introduce any API. It is used to create a reducer function that serves a component. There is only one. Judging the type provides different operations.

The reducer function receives two parameters. The first parameter is preState, which is actually the current State (because a new State will be returned after this operation, so it is called preState, which means the previous State), and the second parameter is action.

The reducer will execute once after the redux is loaded, and a default value will be returned this time.

/*
 1. This file is used to create a reducer for the service of Count component. It is essentially a function
 2. The function receives two parameters, the first is the previous preState, and the second is the action object action (including behavior and data)
*/
export default function countReducer(preState, action) {
    console.log(preState);
    console.log(action);
    const { type, data } = action
    switch (type) {
        case "increase":
            return preState + data
        case "decrease":
            return preState - data
        default:
            return 99  // Default value for initial return
    }
}

be careful:

  • If the preState is the original value, you can modify itself and then return it. If it is a reference value, remember not to modify itself and then return it, because redux will compare the return value with the address of the preState. If it is the same address, the update will fail.
// If it is the original value, this operation does not affect the result
case "increase" :  
	preState += data 
	return preState

// If it is a reference value, the following operation is wrong
case "pop" :{
    preState.pop()
    return preState
}
  • reducer requires a pure function. But if you don't save it, you won't report an error. The storage function needs to meet the following operations
1. Do not modify parameters (even the original array, try not to modify it)
2. It should not have other side functions. If you send a network request, connect to other input and output devices
3. You cannot use functions that do not exist, such as Date.now() , Math.random() The return value is uncertain and not unique API. 
store

The store file is the core of redux.

To create a Store, you need to import the createStore API from redux. It is a function. This function returns a Store.

The createStore function can take three parameters.

The first parameter is reducer (required). Or reducers (the combination of multiple reducers, which will be discussed later).

The second parameter is the initial state (not required). If this parameter is not given, the third parameter becomes the second parameter.

The third parameter is used to support asynchronous action s (not required, which will be described later). If there is no second parameter, this parameter is the second parameter.

import { createStore } from 'redux'

// Introduce reducer for Count special service
import CountReducer from './reducers/count'
// Exposure state
export default createStore(CountReducer)

In this way, the creation and basic code of redux are completed

Next, learn several API s of store and how to use store

store.dispatch(action)

This is the only way to issue an action. When the component gets the action and the store, it can call the dispatch method and pass the action to the store

/**  Count Component  **/

// Import store
import store from '../../redux/store'
// Import the function that creates the action
import { createIncrease } from '../../redux/actions/count' 

	// Method of component instance
    incread = () => {
        store.dispatch(
            createIncrease(1)
        )
    }
    
// When there are few actions, we may not need the action function.
        store.dispatch(
            {
                type:"increase",
                data:1
            }
        )
store.getState()

store.getState() is an API to get the current data of the store. That is, get the last return value of the reducer.

// Import store
import store from '../../redux/store'

// In assembly
render(){
    return (<h1>The result is:{store.getState()}</h1>)   // The result is: 99
}
store.subscribe()

We know that when the state of the component changes, the component will be re rendered. However, neither store nor reducer will actively give the changed data to the component, or tell the component to update it. Therefore, the store provides a subscribe API. When the store is refreshed, it will execute the subscribe function and call the callback function passed to the subscribe.

Therefore, using this API, we can refresh the components when the store changes

/** scr/index.js  **/

import React from "react";
import ReactDOM from "react-dom";
import App from './App';
// Import stre
import store from './redux/store'

ReactDOM.render(< App />, document.querySelector("#root"));

// When the store is refreshed, the component is re rendered, and getState is called again to obtain a new state.
store.subscribe(() => {
    ReactDOM.render(< App />, document.querySelector("#root"));
})
// In this way, all components can get new store data
Completion example - addition and subtraction calculator

redux related files and codes have been completed in the above introduction. Next, let's look at the complete component code. Note that the subscribe API needs to be used in scr/index.js

import React, { Component } from 'react'
// Import store
import store from '../../redux/store'
// Import action
import { createIncrease, createDecrease } from '../../redux/actions/count'

export default class Count extends Component {
    incread = () => {
        store.dispatch( createIncrease(1))
    }
    decread = () => {
        store.dispatch(createDecrease( 1))
    }
    
    render() {
        return (
            <div>
                {/* Display results */}
                <h1>The result is:{store.getState()}</h1>&nbsp;
                {/* Click plus one */}
                <button className="btn btn-default" onClick={this.incread} >+</button>&nbsp;&nbsp;
                {/* Click minus one */}
                <button className="btn btn-default" onClick={this.decread} >-</button>
            </div >
        )
    }
}

data sharing

The main purpose of redux is multi state data sharing. So this function is very important.

In the previous basic use, we only created a reducer. Then take the reducer as the first parameter of createStore(). If we use redux, there will hardly be only one reducer. How can multiple reducers be assembled into one store, and how can components and data be obtained? This is the question to be discussed next.

combineReducers

This is an API exposed by redux. It is used to collect all reducer s together, return the value, and then pass it to createStore () as the first parameter. Then the data of different components can be saved to the store in a centralized way. You can get it from the store if necessary

// Import combineReducers additionally
import { createStore ,combineReducers} from 'redux'


// Introducing Count reducer
import CountR from './reducers/countR'

// Introducing other reducer s
import OtherR from './reducers/otherR'

// Call combineReducers to pass in the object. Return as the first parameter of createStore
const allReducer = combineReducers({
    data1:CountR,   // For the convenience of explanation, you can't just say that
    data2:OtherR
})
// Create store
export default createStore(allReducer)

Except for adding a reducer, the above code does not need to be modified (the same as the basic use)

Then there is how to get the data

Although the store has saved multiple states, the data is still obtained through store.getState().

When there is only one reducer, the return value of store.getState() is the new state returned by the reducer after processing

When there are multiple reducers, store.getState() returns an object, which can be understood as the object passed to combineReducers, but the value of the key is the return value after each execution of the corresponding reducer.

// If the parameter of combineReducers is so

// const allReducer = combineReducers({
//    data1:CountR, / / for convenience of explanation, this is not the case
//    data2:OtherR
// })

// Get the status returned by the corresponding reducer in the component
store.getState().key
// For example, get the status returned by the reducer of Count
store.getState().data1

Asynchronous action

In the above example, the action s are synchronous, that is, the result is immediately increased by one after clicking Add one. If there is an asynchronous operation, such as sending a request or executing it after counting down a few seconds, what should be done?

Suppose we have such a requirement, we hope to add a button based on the above example, and then add 1.5% 3 seconds after clicking

If the asynchronous operation is placed in the view container can be a component, then the asynchronous addition is indeed a very convenient operation.

Asyncdecread = () => {
        setTimeout(() => {
        store.dispatch(
            createIncrease(1)
        )
    }, 3000);
}

However, we want components to display status. If there are few asynchronous operations, it is really convenient and easy to modify asynchrony in components.

But once there are more asynchronous operations and more complex data processing, the component should not undertake these functions.

Of course, Store can't do asynchronous operation. reducer is a pure function and only performs State modification. So the task of implementing asynchronous action is still assigned to action Creators

applyMiddleware

I tried to write asynchronous operations directly in action

setTimeout(() => {
    return { type: "increase", 1 }
}, 3000);

Click Add one to report an error in the execution result. The error message says that action Creators should return an object instead of undefined

Why? Didn't you return an object in 3 seconds? This is related to the asynchronous function execution mechanism of JavaScript. This is a long story.

In short, it just can't.

To implement asynchronous action s, we need to use something called applymeddleware

applyMiddleware is a function that can transform store.dispatch(). Let's perform other operations, such as asynchronous operations, after executing dispatch () and before executing reducer.

applyMiddleware is an API provided by redux. Can be passed in as the second or third parameter of createStore().

export default createStore(reducer, applyMiddleware())

We know that action Creators cannot be received by store.dispatch if they return an action after performing an asynchronous operation. Therefore, we can turn our thinking to execute asynchronous operation and then execute dispatch().

Applymeddleware supports this.

// assembly
store.dispatch(createIncreaseaAsync(1, 3000))   // Means one after three seconds

// action Creator 
export let createIncreaseaAsync = (data, delay) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch({type:"increase",data:data})
        }, delay);
    }
}

Here, we directly return a function to perform asynchronous operations inside the function. After that, send the dispatch again. This dispatch method is equivalent to the store.dispatch method. It came from applymeddleware.

In this way, we have solved the asynchronous problem. However, there is another problem. action Creators require that objects be returned, not functions.

Therefore, we need to use a middleware. It's called Redux thunk

redux-thunk

Redux thunk is a middleware that allows action Creators to return functions and dispatch() to accept function parameters.

middleware - middleware, which is used as the parameter of applyMiddleware.

Use process

  • install

yarn add redux-thunk

perhaps

npm install redux-thunk

  • Import

import thunk from 'redux-thunk'

  • use

export default createStore(allReducer, applyMiddleware(thunk))

In this way, we can successfully execute the asynchronous action.

Asynchronous action implementation code

store.js

// Additional import of applyMiddleware
import { createStore, applyMiddleware} from 'redux'
// Import thunk
import thunk from 'redux-thunk';
// Introducing Count reducer
import reducer from './reducers/count'

export default createStore(reducer, applyMiddleware(thunk))

action Creators

// Expose the method of creating action addition and subtraction 
export let createIncrease = (data) => ({ type: 'increase', data })
export let createDecrease = (data) => ({ type: 'decrease', data })

// Export asynchronous actiob
export let createIncreaseaAsync = (data, delay) => {  // Receive two parameters, data and timer countdown long
    // Return function
    return (dispatch) => {  // Function received a dispatch method
        setTimeout(() => {
            dispatch(createIncrease(data))  // After the asynchronous operation is completed, the action occurs again in the dispatch 
        }, delay); 
    }
}

reducer

Refer to the reducer part of the basic code used by redux.

Count component note: use store.subscribe() to re render the component when the state is changed

import React, { Component } from 'react'
// Import store
import store from '../../redux/store'
// Import action and asynchronous action 
import { createIncrease, createDecrease, createIncreaseaAsync} from '../../redux/actions/count'

export default class Count extends Component {
    incread = () => {
        store.dispatch( createIncrease(1))
    }
    decread = () => {
        store.dispatch(createDecrease(1))
    }
    // Asynchronous action 
    increadAsync= ()=>{
        store.dispatch(createDecrease(1,3000))
    }
    render() {
        return (
            <div>
                {/* Display results */}
                <h1>The result is:{store.getState()}</h1>&nbsp;
                {/* Click plus one */}
                <button className="btn btn-default" onClick={this.incread} >+</button>&nbsp;&nbsp;
                {/* Click minus one */}
                <button className="btn btn-default" onClick={this.decread} >-</button>&nbsp;&nbsp;
                {/* Click asynchronous plus one */}
                <button className="btn btn-default" onClick={this.increadAsync} >Asynchronous execution</button>
            </div >
        )
    }
}

6.5 react-redux

React Redux was officially released by react. Its function is to make it more convenient for react to use redux.

However, it is not necessary to use it, because it requires learning cost and the amount of code itself may increase. And there are new rules.

Why use react Redux

  • It distinguishes between UI components and container components, and further distinguishes between logic and UI.
  • It provides efficiency and reduces unnecessary re rendering. Some states that are not updated do not result in re rendering of components.
  • It automatically monitors state changes and automatically refreshes components.

Schematic diagram of react Redux

Before using it, let's take a look at its working principle. Facilitate the next study.

Before, redux was directly related to components. The component requires direct store.getState(), that is, direct store.dispatch()

React redux divides components into container components and UI components. redux writing does not need to be changed.

The container component passes the required state of the UI component and the function of creating action to the UI component through props. The UI component is responsible for displaying and executing the function modification status. However, the UI component will not introduce the store and the method of creating action. The implementation of these logic is all the responsibility of the container component.

To put it bluntly, UI components display data to me and execute methods to me. I don't care what comes and how to implement the method.

Here, let's look at the requirements of two components:

UI components
  • It is only responsible for UI rendering without any business logic
  • Methods of state and operational state are provided by the container component
  • Do not use any Redux API s
Container assembly
  • Responsible for managing data and business logic, not UI presentation
  • With internal status
  • Use Redux's API

Concrete implementation

install

yarn add react-redux

perhaps

npm install react-redux

Create component

Because react Redux divides components into container components and UI components. Therefore, we can be divided into two documents.

connect

connect() is an API exposed by react redux. It is used to connect container components and UI components.

This method needs to pass two function parameters. And they are all required parameters

First parameter

mapStateToProps: literally means mapping state to props. It is a function that takes out the required state data and finally returns an object. The key value in this object will be passed to the props property of the UI component

This function receives an argument. The data of this parameter is equivalent to the data returned by store.getState().

function mapStateToProps(state){  // Called by react redux. The parameter state. Will be passed in, that is, the state after reducer processing
    return {
        count:state   //  count will be transferred to props of UI component
    }
}

Second parameter

mapDispatchToProps: this means mapping a dispatch to props. It is a function that returns an object. The value corresponding to the key of the object is a function that executes the dispatch method. Dispatch is a parameter passed in by react redux.

function mapDispatchToProps(dispatch){   // Pass in parameter dispatch
    return {
        fun1:(data)=>{dispatch(createIncrease(data))},  // This method will be passed into Props of UI component
        fun2:(data)=>{dispatch(createDecrease(data))},
    }
}

Therefore, when the function is passed to the UI component, the component calls the function, which is equivalent to calling dispatch.

Abbreviation

  • When not many states are needed, we can directly use mapStateToProps as the parameter of connect in the form of anonymous arrow function.

  • The second parameter can be an object directly, and repeated calls to dispatch may not meet the purpose of react redux. Therefore, we only need to replace the value corresponding to the key with action Creator, and then react Redux will handle it. When the component is called, it is equivalent to calling dsipacth.

connect(
    state => ({ count: state}),
    {
        fun1: createIncrease   // Parameters are normally received. The UI component calls fun1(data), and this parameter will be passed to the createIncrease function.
        fun2: createDecrease
    }
 )

connect return value

connect returns a function. In fact, the returned function is the function that really creates the container component.

Yes, we didn't write the container component ourselves. It was created by react redux.

The function with the return value of connect receives a parameter, which is a UI component. The last return value is the container component.

// Introducing UI components
import CountUI from '../../component/Count'  // The code will be posted below
// Gets the function that creates the container component
let containerCreator = connect(mapStateToProps,mapDispatchToProps)
// Create container component
export default containerCreator(CountUI)  // Exposed container assembly

// Abbreviation
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

Once the container component is created, it's time to use it

In the container component, we did not import the store. Store requires props that we pass to the container component when using the container component. And the key is called store

import React, { Component } from 'react'
// Import container components
import Count from './container/Count'
// Import store
import store from './redux/store'

export default class App extends Component {
  render() {
    return (
      <div>
        <Count store={store} />
      </div >
    )
  }
}

Because the sub component of the container component is the UI component, we only need to render the container component, and the UI component will be displayed naturally.

Provider

Provider is a component exposed by react redux. This component has no other functions and is not responsible for rendering the UI. It only does one thing, which is to pass the store to all container components. Therefore, we do not need to use a container component to manually transfer the store.

Generally, we use it in the index.js file under scr, which can cover the container components used.

import React from "react";
import ReactDOM from "react-dom";
import App from './App';

// Import store
import store from './redux/store'

// Import into react Redux Provider component
import { Provider } from 'react-redux'

ReactDOM.render(
<Provider store={store} >
    < App />
</Provider>, 
document.querySelector("#root"));

If you remember the previous store.subscribe(), you will find that we don't use it here. Because react Redux will help us monitor the state changes in the store and update it automatically.

Specific code

container/Count.js

// Introducing UI components
import CountUI from '../../component/Count'

// Introduce connect to connect UI and reudx
import { connect } from 'react-redux'

// Introduce action
import {createIncrease,createDecrease,createIncreaseaAsync} from '../../redux/actions/count'

// The second parameter is easy to write
const mapDispatchToProps={
    fun1:createIncrease,
    fun2:createDecrease,
    fun3:createIncreaseaAsync
}


// Call the connect function, return a function, and then call the returned function. Returns a container component. This component connects the UI component and redux
export default  connect(
    state=>({count:state}),
    mapDispatchToProps)(CountUI) // Connect UI components

component/Count.jsx

import React, { Component } from 'react'
// store cannot be imported

export default class Count extends Component {
    incread = () => {
        // Call method
        this.props.fun1(1)

    }
    decread = () => {
        this.props.fun2(1)
    }
    increadAsync = () => {
        this.props.fun3(1,3000)
    }
    render() {
        return (
            <div>          {/** Get status**/}
                <h1>The result is:{this.props.count}</h1>&nbsp;
                <button className="btn btn-default" onClick={this.incread} >+</button>&nbsp;
                <button className="btn btn-default" onClick={this.decread} >-</button>&nbsp;
                <button className="btn btn-default" onClick={this.increadAsync} >Asynchronous refueling</button>&nbsp;
            </div >
        )
    }
}

redux related files remain unchanged

Use the container component of Count in App.jsx, use the Provide group price in index.js and pass in the store

reference resources
One of the concepts of redux: introduction to redux
Ruan Yifeng's redux tutorial
The redux part of the react tutorial

13000 words, really dry to tears ~.
Ruan Yifeng's tutorial and Silicon Valley's tutorial are very good. I write these not only to facilitate my future review, but also to help people in need of learning.
redux has developed a developer tool to detect, test and review the state. It works well.
I won't explain the use of this. Shang Silicon Valley courses are taught. Just watch the video

Use of redux developer tools
If your Google viewer can't access the Internet, you can download the plug-in from this website
crx4 download redux devtools

Tags: Front-end React redux

Posted on Mon, 20 Sep 2021 20:21:18 -0400 by bigscanner