In this paper, a simple example will be used to explain the use and principle of React-Redux. This example will be written for both redux and React-Redux to deepen understanding.

The example we’re going to implement is a simple one that has a button and a number on the page, and when you click the Add button, the number increases by 1. This is a minimalist example, but it already covers the core functions of data responsiveness and event interaction.

The screenshot of the implementation example page is as follows:

redux

First, let’s take a look at how Redux implements this functionality (see the principles and implementation of Redux in the previous article) :

A Store instance is created using the createStore provided by Redux, which receives a Reducer function as a parameter. The code implementation is as follows:

// store.js
import {createStore} from 'redux';

function countReducer(state=0, action) {
    if (action.type === 'ADD') {
        return state + 1;
    }
    return state;
}

const store = createStore(countReducer);

export default store;
Copy the code

The Store provides three methods for external calls: Dispatch, Subscribe, and getState.

  • Dispatch: by calldispatch(action)Tell the store what behavior to perform, and the Store calls the Reducer function to perform the specific behavior update state. The above example exemplifies when we callstore.dispatch({action: 'ADD'}), then store will be calledcountReducerThe function executes and returns the new state, so the current state is +1.
  • Subscribe: This method provides a function to subscribe to state changes, that is, the subscribed function is executed every time the state changes. For example when we callstore.subscribe(fn); Fn will be executed when state changes in store.
  • GetState: To get the current state, we can get the current and latest state by calling store.state().

Be careful: Dispatch and subscribe can be interpreted as publishing and subscribing.

Once we have the Store, we can write our business component as follows:

import React, {Component} from 'react';
import store from '.. /store'; / / into the store

export default class ReactReduxPage extends Component {
    componentDidMount () {
        Subscribe to state changes while the component is hanging
        store.subscribe((a)= > {
            this.forceUpdate(); // Rerender the component whenever state changes})}// Dispatch an ADD action when the reducer method in the store is updated
    add () {
        store.dispatch({
            type: 'ADD'
        })
    }

    render () {
        return (
            <div>
                <div>number: {store.getState()}</div>
                <button onClick={this.add}>add</button>
            </div>)}}Copy the code

Subscribe, Store. dispatch, and store.getState are called by redux, and when state is updated, render is retriggered. Complex business logic can still be a bit of a hassle, so the authors of Redux packaged a redux module specifically for Use with React, called React-Redux.

react-redux

React-redux is a further encapsulation of Redux, but the underlying redux is still used. Let’s start with react-redux and see how it works with a small example in this article. Redux-react encapsulates redux further by implementing a react-Redux framework of its own.

React-redux implements counters

We implement the same example with react-redux:

Provider

React-redux provides a Provider component that provides a store to all its children. It uses the React context property as follows:

// import file index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';


// ========= key =========
import {Provider} from "react-redux";
import store from './store';
// ========= key =========



ReactDOM.render(
  // Outsource a layer of providers to App components
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById('root'));Copy the code

We introduced Provider and store in the entry file index.js. Then use the Provider to inject the Store into all the child components, and all the child components can be retrieved from the Store. This feature uses the React context property. See the React documentation for details about the context.

store

The code for store.js is the same as for redux, so I won’t repeat it here.

connect

Then we started writing our business component, which now looks like this:

import React, {Component} from 'react';
import {connect} from 'react-redux';

/ / the point! Connect high-level components are used
// Connect connect(mapStateToProps, mapDispatchToProps)(originalComponent)
// Connect (mapStateToProps, mapDispatchToProps)(originalComponent) returns a new component
export default connect(
    state= > ({count: state}),
    dispatch => ({add: () = > dispatch({type: 'ADD'})})
)(
    class ReactReduxPage extends Component {
        render () {
            const {count, add} = this.props
            console.error('this.props:'.this.props);
            return (
                <div>
                    <div>number: {count}</div>
                    <button onClick={add}>add</button>
                </div>)}})Copy the code

As shown above, when we write components using react-redux, the properties and methods of the components are obtained from props. For example, this.props. Count and this.props. Add in the above example. Where did the properties and methods in props come from? The connect function accepts two functions, mapStateToProps and mapDispatchToProps, as shown in the following example:

MapStateToProps function: state => ({count: state}) mapDispatchToPropsdispatch= > ({add: (a)= > dispatch({type: 'ADD'})})
Copy the code

Both functions return an object in which the key/value pairs are used as props:

The mapStateToProps function returns obj1: {count: state} mapDispatchToProps returns obj2: {add: (a)= > dispatch({type: 'ADD'})} The final props for the component is: {... obj1, ... Obj2} is {obj2}count: state,
    add: (a)= > dispatch({type: 'ADD'}}Copy the code

When we click the add button, we call this.props. Add, which calls Dispatch ({type: ‘ADD’}), and the reducer method in the store will update the map-reduce state. After updating the state, react-redux will render the components again.

React -redux source code implementation

React-redux provides two methods, Provider and connect, for react-redux.

// react-redux.js
import React, {Component} from 'react';

export const connect = (
    mapStateToProps,
    mapDispatchToProps
) => WrappedComponent= > {
    // Execute the code logic inside the function
    // return a component
}

export class Provider extends Component {
    render () {
        // return ...}}Copy the code

Provider source code implementation

Provider provides stores so that all components in the component tree can obtain store instances. Let’s review how the main entry file index.js uses Provider to provide stores down:

// import file index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';


// ========= key =========
import {Provider} from "react-redux";
import store from './store';
// ========= key =========



ReactDOM.render(
  // Outsource a layer of providers to App components
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById('root'));Copy the code

Then we’ll implement the react-redux Provider ourselves:

// react-redux.js
import React, {Component} from 'react';

// Create a context with an arbitrary name
// Context allows us to pass values deep into the component tree without explicitly traversing each component.
const StoreContext = React.createContext();

/ / the point! The realization of the Provider
export class Provider extends Component {
    render () {
        // Use a Provider to pass the current store to the following component tree.
        // Any component, no matter how deep, can read this value.
        return (
           <StoreContext.Provider value={this.props.store}>
               {this.props.children}
           </StoreContext.Provider>)}}Copy the code

The react context is used to create a Provider. The react context is used to create a Provider.

React.docschina.org/docs/contex…

We do this by creating a context, StoreContext, and passing stores down the component tree with storeContext.provider. A component in the component tree passes static contextType = StoreContext; The this.context in the component points to the value provided by storeContext. Provider, which is the store instance. Context will not be described too much, read the official documentation, the above code will make sense.

Connect source code implementation

Connect is a double-arrow function that returns a component.

Executed first connect (mapStateToProps, mapDispatchToProps) returns a function, then the function to receive a component as a parameter, finally return a new component, it is the use of the high order component.

So we use the connect way so the connect (mapStateToProps mapDispatchToProps) (WrappedComponent), WrappedComponent is a pure components, it receives only props, Components are rendered using props without holding state internally. Props is provided by the mapStateToProps and mapDispatchToProps functions. The reason for the react-Redux design is that the WrappedComponent is only responsible for receiving props and rendering components. It doesn’t care about the state, so the logic of the WrappedComponent is simpler and its responsibilities are clearer. Responsible for data management and the logic of these operations are carried out on the connect (mapStateToProps mapDispatchToProps) (WrappedComponent) returns the component after complete, will be around here, and later by writing code in detail.

When we perform the connect (mapStateToProps mapDispatchToProps) (WrappedComponent), You can use the parameters mapStateToProps, mapDispatchToProps, and WrappedComponent inside the function. The connect function returns a new encapsulated component that uses the mapStateToProps, mapDispatchToProps, and WrappedComponent parameters to handle the logic.

Can connect not use the double arrow function? That’s ok, because the connet function ultimately returns a new component, and when encapsulating that new component you need mapStateToProps, mapDispatchToProps, and WrappedComponent to do something. You don’t need double arrows to do that. We can do the same thing by writing it as follows:

export const connect = (
    mapStateToProps,
    mapDispatchToProps,
    WrappedComponent
) => {
    // Execute the code logic inside the function
    // return a component} then connect(mapStateToProps, mapDispatchToProps, WrappedComponent) does not need connect(mapStateToProps, WrappedComponent) mapDispatchToProps )(WrappedComponent)Copy the code

React-redux has two arrows:

  • The functions of the first arrow function are used to map props, and the parameters of the second arrow function are used to pass the components to be encapsulated
  • 2, force lattice is relatively high, enough SAO qi, make full use of the principle of closure and function scope

We’ll implement a version that renders the contents of the WrappedComponent. We’ll focus on the highlights. Since the component that connect returns is inside the Provider, we can get the store instance, so we can call the store getState method to get the latest state. Then we call mapStateToProps(state) to get the stateProps to pass to the WrappedComponent.

export const connect = (
    mapStateToProps,
    mapDispatchToProps
) => WrappedComponent= > {
    return class extends Component {
        // Specify contextType to read the current store context.
        // React will look up the nearest store Provider and use its value.
        static contextType = StoreContext;
        constructor(props) {
            super(props);
            this.state = {
              props: {}}; }// ============== key ==============
        componentDidMount () {
            this.update();
        }

        update () {
            // this.context already points to our store instance
            const {dispatch, subscribe, getState} = this.context;

            // Call mapStateToProps with the latest state of the store as an argument
            // Return the props we want to pass to WrappedComponent
            let stateProps = mapStateToProps(getState());

            // Call setState to trigger render to update the WrappedComponent content
            this.setState({
                props: {
                    ...stateProps
                }
            })
        }

        render() {
            return <WrappedComponent  {. this.state.props} / >} // ============== key ==============}}Copy the code

The above code already shows our initialization interface, but it doesn’t handle events yet, so we need to use mapDispatchToProps to generate a mapping of events to props. Let’s review the two functions:

MapStateToProps function: state => ({count: state}) mapDispatchToPropsdispatch= > ({add: (a)= > dispatch({type: 'ADD'})})
Copy the code

Add the following two lines of code to handle events: call mapDispatchToProps to generate events related props to pass to the WrappedComponent.

update () {
    // this.context already points to our store instance
    const {dispatch, getState} = this.context;

    // Call mapStateToProps with the latest state of the store as an argument
    // Return the props we want to pass to WrappedComponent
    let stateProps = mapStateToProps(getState());

    // Call mapDispatchToProps to return the functions associated with the event
    let dispatchProps = mapDispatchToProps(dispatch); / /! Key new code

    // Call setState to trigger render to update the WrappedComponent content
    this.setState({
        props: {... stateProps, ... dispatchProps/ /! Key new code}})}Copy the code

We need to re-render the component when state changes, so we also need to add a subscription function:

componentDidMount () {
    this.update();

    // ===== new code =====
    const {subscribe} = this.context;
    // Rerender the component when state changes
    subscribe ((a)= > {
        this.update() 
    })
    // ===== new code =====
}
Copy the code

React-redux react-redux

// react-redux
import React, {Component} from 'react';

// Create a context with an arbitrary name
// Context allows us to pass values deep into the component tree without explicitly traversing each component.
const StoreContext = React.createContext();

export const connect = (
    mapStateToProps,
    mapDispatchToProps
) => WrappedComponent= > {
    return class extends Component {
        // Specify contextType to read the current store context.
        // React will look up the nearest store Provider and use its value.
        static contextType = StoreContext;
        constructor(props) {
            super(props);
            this.state = {
              props: {}}; }// ============== key ==============
        componentDidMount () {
            this.update();

            const {subscribe} = this.context;
            // Rerender the component when state changes
            subscribe ((a)= > {
                this.update()
            })
        }

        update () {
            // this.context already points to our store instance
            const {dispatch, getState} = this.context;

            // Call mapStateToProps with the latest state of the store as an argument
            // Return the props we want to pass to WrappedComponent
            let stateProps = mapStateToProps(getState());

            // Call mapDispatchToProps to return the functions associated with the event
            let dispatchProps = mapDispatchToProps(dispatch); / /! Key new code

            // Call setState to trigger render to update the WrappedComponent content
            this.setState({
                props: {... stateProps, ... dispatchProps/ /! Key new code
                }
            })
        }

        render() {
            return<WrappedComponent {... This. State. Props} / >} / / = = = = = = = = = = = = = = key = = = = = = = = = = = = = =}} export class Provider extends Component {render () {/ / Use a Provider to pass the current store to the following component tree. // Any component, no matter how deep, can read this value. return ( <StoreContext.Provider value={this.props.store}> {this.props.children} </StoreContext.Provider> ) } }Copy the code

MapDispatchToProps can also be used as an object, for example, funtion

The code for the business components is also posted

// Business component code
import React, {Component} from 'react';
import {connect} from 'react-redux';

/ / the point! Connect high-level components are used
// Connect connect(mapStateToProps, mapDispatchToProps)(originalComponent)
// Connect (mapStateToProps, mapDispatchToProps)(originalComponent) returns a new component
export default connect(
    state= > ({count: state}),
    dispatch => ({add: () = > dispatch({type: 'ADD'})})
)(
    class ReactReduxPage extends Component {
        render () {
            const {count, add} = this.props
            console.error('this.props:'.this.props);
            return (
                <div>
                    <div>number: {count}</div>
                    <button onClick={add}>add</button>
                </div>)}})Copy the code

Entry js code:

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


// ========= key =========
import {Provider} from "react-redux";
import store from './store';
// ========= key =========



ReactDOM.render(
  // Outsource a layer of providers to App components
  <Provider store={store}>
    <App />
  </Provider>.document.getElementById('root'));Copy the code

React-redux: React-redux