“This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

This article focuses on the basic usage of redux and React-redux

A story,

1.1 introduction

(1) Concept

  • It’s a special one for doingState managementJs library (Not react plugin)
  • Can be used invue,react,angularIn the project, it works with React
  • Function: Centrally manages state shared by multiple components in the React application

(2) When should REdux be used

  • The state of a component that needs to be readily available to other components (Shared)
  • One component needs to change the state of another component (communication)
  • General principle: can not use, if not more difficult to use

1.2 Workflow

The redux workflow flow chart is as follows:

Three core concepts:

(1) the action

  • Object of action
  • Contains two properties
    • type:Attribute identification, the value is a string, unique, required attribute
    • data:Data attributes, value of any type, optional property
    • example{type:'ADD_STUDENT', data: {name: 'tom', age: '18'}}

(2) the reducer

  • Used forInitialization, processing state
  • Generate a new state based on the old state and action during processingPure functions

(3) the store

  • willstate,action,reducerThe object of connection
  • During processing, pure functions of the new state are generated based on the old state and action

1.3 store

The directory structure

│ ├─ ├─ exercises // exercises, Folder for different module │ │ ├ ─ count. Js │ │ └ ─ person. Js │ ├ ─ the js / / action type, unique identification constant │ ├ ─ index. The js / / entry documents │ └ ─ reducers / / │ ├─ ├─ index.js // Reducers │ ├─ persons.js │ ├─ ├.js // reducers │ ├─ persons.jsCopy the code

CreateStore was introduced to create the core Store object in Redux, while Redux-Thunk and applyMiddleware were used to support asynchronous actions

// src/store/index.js
import { createStore, applyMiddleware } from "redux";
// Support asynchronous actions
import thunk from "redux-thunk";
import reducers from "./reducers";

export default createStore(reducers, applyMiddleware(thunk));
Copy the code

1.4 the action

Defines the constant value of type type in the Action object, eliminating the magic string

// src/store/constants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
Copy the code

Create actions. Regular actions return objects and asynchronous actions return functions

// src/store/actions/count.js
import { INCREMENT, DECREMENT } from ".. /constants";

// The normal action is object '{type: INCREMENT, data}'
export const increment = data= > ({ type: INCREMENT, data });
export const decrement = data= > ({ type: DECREMENT, data });

// The value of asynchronous action is a function
export const incrementAsync = (data, time) = > {
  return (dispatch) = > {
    setTimeout(() = > {
      dispatch(increment(data));
    }, time);
  };
};
Copy the code

Asynchronous action

  • Deferred actions do not want to be given to the component itself, they want to be given to the action
  • When asynchronous action is needed: You want to operate on the state, but the specific data is returned by the asynchronous task

You don’t have to write asynchronous actions. You can wait for the results of asynchronous tasks before distributing synchronous actions

Instead of returning an object, the created action returns a function. After the asynchronous task has a result, a synchronous action is distributed to actually manipulate the data

1.5 reducer

  • reducerThe function takes two arguments:Previous state (preState).Action object (Action)
  • fromactionFetch from objecttype.data
  • According to thetypeDecide how to process the data
import {INCREMENT, DECREMENT} from '.. /constants'
// Initialization state
const initState = 0;

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

(1) Initialization state

If no initial value is set in reducer, console.log(preState, action) is output on the console as shown below: preState=undefined, type=@@redux/ initq.p.v.O.D.W

Type redux is internally processed as a random value, and the output is different each time

throughconst initState = 0; export default function count(preState = initState, action) {.... default: return preState; }Setting the initial value

(2) Reducer is a pure function

// src/store/reducers/persons.js
import { ADD_PERSON } from ".. /constants";

const initState = [{ id: "001".name: "jona".age: "23" }];

export default function persons(preState = initState, action) {
  const { type, data } = action;
  switch (type) {
    case ADD_PERSON:
      Unshift (data) // Return preState. Unshift (data) // This should not be done because preState has been overwritten, the personReducer function is not pure, redux does not recognize state changes, and the view is not updated
      return [data, ...preState];
    default:
      returnpreState; }}Copy the code

Supplement the concept of pure functions

(3) Combine reducers

Using combineReducers merge, the received argument is an object whose key value is the same as that obtained by getState()

// src/store/reducers/index.js
import { combineReducers } from "redux";
import count from "./conut";
import persons from "./persons";

export default combineReducers({
  count,
  persons,
});
Copy the code

1.6 getState and dispatch

  • Components throughgetState()Take the store data
  • throughdispatchTriggering status updates
import store from ".. /.. /store";

import { increment } from ".. /.. /store/action/count";

 / / add
  clickIncrement = () = > {
    const { value } = this.selectNumber;
    store.dispatch(increment(value * 1));
  };
 render() {
    return (
      <div>
        <h1>The current sum is: {store.getstate ()}</h1>.<button onClick={this.clickIncrement}>+</button>
      </div>)}Copy the code

View not updating problem

dispatchAfter the discoverystoreThe value of theta has changed butView not updated, the following figureRedux does not support automatic updatessubscribeAPI to monitorreduxState change, only change, needCall render again

componentDidMount() {
    store.subscribe(() = > {
      this.forceUpdate();
    });
}
Copy the code

Second, the react – story

2.1. The react – redux VS redux

For ease of use, the authors of Redux have packaged a react-redux library dedicated to React

The differences between the two warehouses are as follows:

  • reduxYou need to listen for store changes to update the view, the use ofstore.subscribe(() => { this.forceUpdate(); }); React-redux does not need to listen
  • react-reduxComponents are divided into UI components and container components; The operations on redux are all in the container component,Single blame principle; And through theconnect(mapStateToProps, mapDispatchToProps)()Connect the container component to the UI component; Redux makes no distinction

2.2 Basic Features of React – Redux

React-redux divides all components into two broad categories: UI components (Presentational Components) and Container Components (Container Components)

(1)UI componentsIt has the following characteristics:

  • Only responsible for the presentation of the UI, with no business logic
  • No state (that is, not usedthis.stateThis variable)
  • All data is defined by parameters (this.props) to provide
  • Without using anyReduxAPI

(2)Container componentsIs quite the opposite

  • Responsible for managing data and business logic, not UI rendering
  • With internal state
  • useReduxAPI

(3)UI componentswithContainer componentsassociated

  • All of theUI componentsAll should be wrapped in oneContainer components, they areFather and son
  • Container components really interact with Redux and can call redux’s API at will
  • You cannot use any Redux apis in UI components
  • The container component passes (1) the state held in redux and (2) the methods used to manipulate the state to the UI component via the props

Summary: The UI component is responsible for rendering the UI, while the container component is responsible for managing data and logic. If a component has both UI and business logic, split it into the following structure: Outside is a container component, inside is a UI component. The former is responsible for communicating with the outside world, passing the data to the latter, who renders the view

2.3. Connect (), mapStateToProps

React-redux provides the connect method for generating container components from UI components

In the code below, CountUI is the UI component, and the container component is the last exported using Connect

To define the business logic, you need to give two pieces of information:

  • Input logic: External data (i.estateObject) is converted to parameters of a UI component
  • Output logic: How do actions emitted by the user become Action objects that flow out of the UI component

The connect method accepts two parameters: mapStateToProps and mapDispatchToProps. They define the business logic of the UI components. The former is responsible for the input logic, which maps state to the UI component’s parameters (props), and the latter is responsible for the output logic, which maps user actions on the UI component to actions

MapStateToProps receives the state parameter and mapDispatchToProps receives the dispatch parameter

// Container components
import { connect } from "react-redux";
import CountUI from ".. /.. /components/count";
import {
  createIncrementAction,
  createDecrementAction,
  createIncrementAsyncAction,
} from ".. /.. /redux/count_action";

const mapStateToProps = (state) = > ({ count: state });

const mapDispatchToProps = (dispatch) = > ({
  increment: (number) = > {
    dispatch(createIncrementAction(number));
  },
  incrementAsync: (number) = > {
    dispatch(createIncrementAsyncAction(number, 500));
  },
  decrement: (number) = >{ dispatch(createDecrementAction(number)); }});export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
Copy the code
/ / UI components
import React, { Component } from "react";

export default class CountUI extends Component {
  / / add
  increment = () = > {
    const { value } = this.selectNumber;
    this.props.increment(value * 1);
  };

  / / subtraction
  decrement = () = > {
    const { value } = this.selectNumber;
    this.props.decrement(value * 1);
  };

  / / an odd number
  incrementIfOdd = () = > {
    if (this.props.count % 2= = =1) {
      const { value } = this.selectNumber;
      this.props.increment(value * 1); }};/ / asynchronous
  incrementAsync = () = > {
    const { value } = this.selectNumber;
    this.props.increment(value * 1);
  };

  render() {
    return (
      <div>
        <h1>The current sum is: {this.props. Count}</h1>.</div>); }}Copy the code

2.4, mapDispatchToProps

MapDispatchToProps is the second parameter to the connect function that maps UI component parameters to the Store. dispatch method. That is, it defines which user actions should be passed to the Store as actions

It can be a function or an object

(1) If mapDispatchToProps is a function

// Container components
const mapDispatchToProps = (dispatch) = > ({
  increment: (number) = > {
    dispatch(createIncrementAction(number));
  },
  incrementAsync: (number) = > {
    dispatch(createIncrementAsyncAction(number, 500));
  },
  decrement: (number) = >{ dispatch(createDecrementAction(number)); }});// mapDispatchToProps () returns function
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
Copy the code

(2) If mapDispatchToProps is an object

The key value is a function, Action Creator, that returns an Action automatically emitted by Redux

// short for mapDispatchToProps, returns object
export default connect(mapStateToProps, {
  increment: createIncrementAction,
  incrementAsync: createIncrementAsyncAction,
  decrement: createDecrementAction,
})(CountUI);
Copy the code

2.5, the Provider

After the connect method generates the container component, it needs to get the state object from the container component to generate the UI component’s parameters.

One solution is to pass a state object as a parameter to the container component. However, this can be cumbersome, especially if container components are at very deep levels, and passing state down from one level to the next is cumbersome.

// src/App.js
import React, { Component } from "react";
import Count from "./container/count";
import store from "./redux/store";

export default class App extends Component {
  render() {
    return <Count store={store} />; }}Copy the code

React-redux provides a Provider component that lets container components get state

The Provider wraps a layer around the root component so that all child components of the App get state by default

It works by using the React component’s context property

// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from './redux/store'
import App from "./App";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById("root"));Copy the code

Write in the last

All examples of this article are on Github, welcome star, learn together!