State management in React -- Redux

Let's take the function of adding to do list as an example:

In order to develop specifications, we should adopt the fragment writing method of reducer, that is, one type of data and one module

Write specifications without react Redux

Production and installation redux

yarn add redux

Create a store directory under the src directory

Create index.js and reducer.js

index.js

import { createStore } from 'redux'

import reducer from './reducer'

const store = new createStore ( reducer )

export default store

reducer.js

import { combineReducers } from 'redux'

const reducer = combineReducers({
  
})

export default reducer

Create todos folder, and then create

state.js

const state = {
    todos : [
        {
            id : 1,
            text : 'sleep'
        },
        {
            id : 2,
            text : 'having dinner'
        },
        {
            id : 3,
            text : 'Beat beans'
        }
    ]
}

export default state

type.js

export const ADD_TODOS = 'ADD_TODOS'

actionCreators.js

import * as type from './type'
import store from '../index'

const actionCreators = {
    addTodos( value ){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }
        store.dispatch( action )//It is equivalent to the commit in vue and sends the action to reducer
    }
}

export default actionCreators

reducer.js

import * as type from './type'
import state from './state'

const reducer = ( previousState = state,action) => {//Assign state to previousState as the initial value
   let newState = {//Structure to the new state, and then operate on the new state
       ...previousState
   }
   switch (action.type) {
       case type.ADD_TODOS:
            newState.todos.push({
            id : newState.todos.length + 1,
            text : action.payload
            })
            break;
        default:
            break;  
   }
   console.log(newState)
   return newState
}

export default reducer

After these are completed, we need to get the reducer under todos folder to the reducer under src for use, that is, change the reducer.js file under src directory to this

import { combineReducers } from 'redux'
import todos from './todos/reducer'

const reducer = combineReducers({
  todos
})

export default reducer

Such a Redux is completed, and then the component is created and then called store.

Create pages directory, todos directory and todo components under src directory

In the component, we call the method in store. In the two file, TodoInput is to increase the task, so we should call the activeCreators method in TodoInput to perform the task.

TodoInput.js

import React,{ Component,Fragment } from 'react'

import actionCreators from '../../store/todos/actionCreators'//Introducing actionCreators

class TodoInput extends Component {

    addTodos = (e) => {
        let keyCode = e.keyCode
        let value = e.target.value
        if ( keyCode === 13 ){
            actionCreators.addTodos(value)//Call the method in actionCreators
        }
    }
    render () {
        return (
            <Fragment>
                <input onKeyUp={ this.addTodos } type="text"></input>
            </Fragment>
        )
    }
}

export default TodoInput

TodoContent.js

TodoContent shows the data. Here are several key points. The view update in Redux needs to be completed by store.subscribe,

Knowledge points:

  1. List rendering method
  2. How to make the method in actionCreators perform post view update
import React,{ Component,Fragment } from 'react'
import store from '../../store/index'

const Item = (props) => {
    return (
        <li>{ props.item.text }</li>
    )
}
class TodoContent extends Component {

    constructor () {
        super()
        this.state={
            todos : store.getState().todos.todos//The name in reducer to the data name in state in todos
        }
    }

    renderItem = () => {//Best list rendering method
        return this.state.todos.map( (item,index)=>{
           return <Item item={ item } key={ item.id }></Item>
        })
    }

    componentDidMount(){//Method of view update
        store.subscribe(()=>{
            this.setState({
                todos : store.getState().todos.todos
            })
        })
    }
    render () {
        return (
            <Fragment>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

export default TodoContent

index.js

import TodoInput from './TodoInput'
import TodoContent from './TodoContent'

export { TodoInput,TodoContent }

Use react Redux specification

Knowledge points involved in using react Redux

Core concept

Container component (smart component), UI component (puppet component)

React Redux thinks that if a component wants to use the data in the store or the method of actionCreator, we should change it to the way that the container component wraps the UI component

Among them, the container component is responsible for connecting the store and passing the state and method to the UI component. The UI component obtains these APIs from the properties and uses them

Moreover, there is no need to worry that container components can be generated according to UI components

Core API

Provider , connect(mapStateToProps´╝îmapDistpatchToProps)

The Provider is responsible for passing store related APIs into all internal container components

connect is responsible for generating container components according to UI components

Usage and details

We modified it according to the previous case that did not use react redux

Install react Redux in production environment

yarn add react-redux

You need to put the Provider component on the outermost layer of the component and pass in the store for it

Then wrap the App component in the main entry file with the Provider component. Note that the Provider component comes from react Redux, and the store needs to be introduced to assign a value to the Provider

import store from './store'

import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store = { store }>
    <App />
  </Provider>
, document.getElementById('root'));

Use connect to change components that need to use store related APIs into container components and nested UI components

connect The return value of the method is a function that receives UI Component will return a container component, which has been nested inside UI assembly

Provider Components will utilize context The context sets itself in the attribute store Passed to its own child component, and the container component gets context above store dependent api

We can connect Passed in function mapStateToProps/mapDispatchToProps Parameters to control container components UI The process by which a component passes properties

*mapStateToProps Role of*: 

   take store Medium state Pass to UI On the properties of the component
   Value is a function that receives store Medium state
   What is the return value, UI What are the properties of the component
   And because it's already done in the container component store.subscribe So once store When the state changes in the container, the container component will know immediately and give it again UI Component incoming new data


    *problem*:  Pit? ours store The data in is being updated, but the data in the container component inside props Do not update

    Analysis: start from the component, check the code we wrote, and find that the code is OK. At this time, we have to check the data source, and the data source comes from  reducer , Then output the data. It is found that the data is rendered three times. Two times, when a component is created, the data is generated from nothing, and another time, it is generated by the container component. We will send our data later new_state Repeated initialization, 

    Solution: 
        let newState = {...previousState}

mapDispatchToProps Role of:

   Can be used to dispatch Some methods passed to UI On components
   Value is a function that receives store Medium dispatch
   Go back up, UI What are the properties of the component
   This time actionCreator It becomes pure, just create action´╝îdispatch action Your actions can be put mapDispatchToProps In other words, in mapDispatchToProps to UI In the function passed by the component actionCreator Create good action to dispatch go

But the advantages of this writing are uncomfortable because actionCreator There is a method in the, which needs to be mapDispatchToProps Write another method in

So it can be used redux Medium bindActionCreators take actionCreator The method in is put directly into UI Component and return it action Give direct dispatch go


Because it is used in components store The status in is also used actionCreator This component introduces a lot of things. In fact, we can take these logic packages out
import React,{ Component,Fragment } from 'react'
import actionCreators from '../../store/todos/actionCreators'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
 
const Item = (props) => {
    return (
        <li>{ props.item.text }</li>
    )
}
class TodoContent extends Component {

    renderItem = () => {
        return this.props.todos.todos.map( (item,index)=>{
           return <Item item={ item } key={ item.id }></Item>
        })
    }
    addTodos = (e) => {
        let keyCode = e.keyCode
        let value = e.target.value
        if ( keyCode === 13 ){
            this.props.addTodos(value)
        }
    }

    render () {
        console.log('render')

        return (
            <Fragment>
                 <input onKeyUp={ this.addTodos } type="text"></input>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

// Const mapstatetoprops = (state) = > {/ / the first connect method
//     // return state.todos
//     return {
//         todos : state.todos
//     }
// }

// const mapDispatchToProps = ( dispatch ) => {
//     console.log(dispatch)
//     return bindActionCreators ( actionCreators,dispatch )
// }

// export default connect(mapStateToProps,mapDispatchToProps)(TodoContent)

export default connect(//The second connect method
    state => state,
    dispatch => bindActionCreators(actionCreators,dispatch)
)(TodoContent)

Note: if the method and data are used in different components, for example, the input box and list display of the above case are not in the same component, the list display component can only import

export default connect(
    state => state
)(TodoContent)

Task input components need to be used

export default connect(
    state => state,
    dispatch => bindActionCreators(actionCreators,dispatch)
)(TodoInput)

Modify the actionCreators.js file under stor

import * as type from './type'

const actionCreators = {
    addTodos( value ){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }
        return action
    }
}

export default actionCreators

The change is from store.dispatch(action) to return action, because our dispatch function is now left to the component

Asynchronous tool Redux thunk of react Redux

Install Redux thunk

yarn add redux-thunk

Use details

The scenario fetch requests a piece of data (the data request is asynchronous), takes the todo data as the data, and then continues according to the above react redux

An important concept of Redux thunk is the application middleware

We mainly modify index.js in the store directory

import { createStore,applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

import reducer from './reducer'

const store = new createStore( reducer,applyMiddleware(thunk) )

export default store 

The index.js in the new store is like this

Data requests are placed in actionCreators.js

The expression of dispatch (action) in the data request here is equivalent to return action, which will also be changed in use

import * as type from './type'
const actionCreators = {
    getData(){
        return dispatch =>{
            fetch('/data.json')
            .then(res=>res.json())
            .then(data=>{
                console.log('datad',data)
               let action = {
                   type : type.GET_DATA,
                   payload : data
               }
               dispatch( action )
            })
            .catch(error=>console.log(error)) 
        }   
    },
    addTodos(value){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }

        return action
    }
}

export default actionCreators

Modify it in reducer.js

import * as type from './type'
import state from './state'

const reducer = (previousState = state,action)=>{
    let newState = {
        ...previousState
    }
    switch(action.type){
        case type.GET_DATA:
            newState.todos=action.payload
            break;
        case type.ADD_TODOS:
            newState.todos.push({
                id : newState.todos.length + 1,
                text : action.payload
            })
            break;
        default:
            break;
    }
    console.log(newState)
    return newState
}

export default reducer

call

TodoInput.js

import React,{ Component,Fragment } from 'react'
// import { connect } from 'react-redux'
// import  { bindActionCreators } from 'redux'
// import actionCreators from '../../store/todos/actionCreators'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actionCreators from '../../store/todos/actionCreators'

class TodoInput extends Component {
    addTodos = (e)=>{
        let keyCode = e.keyCode
        let value = e.target.value
        if (keyCode === 13){
            this.props.addTodos(value)
        }
    }

    getData=()=>{
        this.props.getData()
    }
    render() {
        return (
            <Fragment>
                <button onClick={ this.getData }>get data</button>
                <input onKeyUp={ this.addTodos } type="text" placeholder="Please enter big Baba"></input>
            </Fragment>
        )
    }
}

export default connect(
    state => state,//Must add
    dispatch => bindActionCreators( actionCreators,dispatch )
)(TodoInput)

There is a special point. If a component only needs state, it can not need the dispatch part. If it wants to dispatch, the data must be

TodoContent.js

import React,{ Component,Fragment } from 'react'

import { connect } from 'react-redux'

const Item = (props)=>{
    return (<li>{ props.item.text }</li>)
}
class TodoContent extends Component {

    renderItem = () => {
        return this.props.todos.todos.map( (item,index)=>{
            return <Item item= { item } key ={ item.id }></Item>
        })
    }
    
    render () {
        return (
            <Fragment>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

// export default TodoContent

export default connect(
    state => state
)(TodoContent)

Summary Redux thunk

Its usage is very simple:

  1. Install Redux thunk
  2. When creating a store, use the middleware import {createstore, applymeddleware} from 'Redux' import thunk from 'Redux thunk' import reducer from '. / reducer' const store = createstore (reducer, applymeddleware (thunk))
  3. At this time, the actionCreator method can return a function that can receive the dispatch. After performing asynchronous operations in this function, we can send the action created by actionCreator to the

Keywords: Javascript React

Added by mightymaster on Sun, 10 Oct 2021 06:45:55 +0300