React-redux introduction and application

The official documentation

Container components/presentation components

Redux’s React binding library is based on the idea of container components and presentation component classes. React-redux divides all components into two broad categories: UI components (Presentational Components) and Container Components (Container Components)

  • UI components

    • Only responsible for the presentation of the UI, with no business logic
    • There is no state
    • All data is provided by the parameter (this.props)
    • Do not use any of Redux’s apis
  • Container components

    • Responsible for managing data and business logic, not UI rendering
    • With internal state
    • Use Redux’s API

    The UI component is responsible for rendering the UI, and the container component is responsible for managing the data and logic, and the connection between the two is connected by CONNECT.

import { createStore } from "redux";

Create a Reducer to define the change rules in state
const countReducer = function(count = 0, action) {
  switch(action.type) {
    case 'ADD':
      return count + 1;
    case 'MINUS':
      return count - 1;
    default:
      returncount; }}// Create a store to store state
const store = createStore(countReducer);

export default store;
Copy the code

Provider

enables connect() methods at the component level to obtain the Redux store. Normally, your root component should be nested within to use the connect() method.

Provider is actually the component created by Context’s < context. Provider value={store}>. Pass the Redux Store to the child component.

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import ReactReduxPage from "./pages/ReactRedux/ReactReduxPage";

import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <div className="center">{/ *<SetStatePage />* /} {/ *<ContextPage />* /} {/ *<HookPage />* /} {/ *<ReduxPage />* /}<ReactReduxPage />
    </div>
  </Provider>.document.getElementById("root"));Copy the code

connect()

connect([mapStateToProps], [mapDispatchToProps])(WrappedComponent)

  • [mapStateToProps(state, [ownProps]): stateProps](Function): If defined, the component will listen for changes in the Redux store. Any time the Redux Store changes, the mapStateToProps function is called. The callback must return a pure object
  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps](Object or Function): If you pass an Object, then every Function defined on that Object is treated as if it were an ObjectRedux action creator, the method name defined by the object as the property name; Each method will return a new function in the functiondispatchMethods willaction creatoThe return value of r is executed as an argument. These properties will be incorporated into the componentprops
/ / UI components
import { Component } from "react";
import { connect } from "react-redux";
import { mapStateToProps, mapDispatchToProps } from "./reactRedux";

class ReactReduxPage extends Component {
  render() {
    console.log(this.props);
    return (
      <div>
        ReactReduxPage - store - {this.props.count}
        <button onClick={this.props.addCount}>new</button>
        <br />
        <button onClick={this.props.minusCount}>To reduce</button>
      </div>); }}export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);

// Generate container components using connect connections
// reactRedux.js
export const mapStateToProps = (state) = > {
  return {
    count: state,
  };
};

export const mapDispatchToProps = (dispatch) = > {
  return {
    addCount: () = > dispatch({ type: "ADD" }),
    minusCount: () = > dispatch({ type: "MINUS"})}; };Copy the code

React-Redux source code implementation

Provider

The Provider essentially wraps a layer around the root component and places a store in the context. So all the child components can get the store from the context

 // context.js
 import React from 'react';
 const Context = React.createContext();
 
 export default Context;
 
 // Provider.js
import Context from './context';
export default function Provider({ children, store }) {
    // Provider is the Context property
    return <Context.Provider value={store}>{children}</Context.Provider>
}
Copy the code

connect

  • The first edition
import { useContext } from 'react';
import { bindActionCreators } from '.. /Redux';
import Context from './context';
/** * Connect is actually a higher-order component that mounts store data to wrappedComponent *@param {Function} mapStateToProps
 * @param {Function | Object} mapDispatchToProps
 * @returns* /
const connect =
  (mapStateToProps, mapDispatchToProps) = > (WrappedComponent) = > (props) = > {
    let stateProps = {},
      dispatchProps = {};
    const store = useContext(Context);
    const dispatch = store.dispatch;
    const subscribe = store.subscribe;

    if(typeofmapStateToProps ! = ='function') {
      throw new Error('mapStateToProps must be a function! ');
    }

    if (typeofmapDispatchToProps ! = ='object' && typeofmapDispatchToProps ! = ='function') {
      throw new Error('mapDispatchToProps must be Function or Object');
    }

    stateProps = mapStateToProps(store.getState());

    // In this function, the Dispatch method will execute the return value of Action Creator as an argument, resulting in actionCreators
    if (typeof mapDispatchToProps === 'function') {
      dispatchProps = mapDispatchToProps(dispatch);
    } else if (typeof mapDispatchToProps === 'object') {
      dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
    }

    return <WrappedComponent {. props} {. stateProps} {. dispatchProps} / >;
  };
export default connect;

Copy the code

Click the button and the States tree in the store will change, but the view will not be re-rendered.

Cause: Changes to store data cannot trigger view updates. Use hooks to customize a forceUpdate that triggers view updates. Refer to the question in the official documentation FAQ: Is there anything like forceUpdate?

// Optimized connect.js
import { useContext, useEffect, useReducer } from "react";
import { bindActionCreators } from ".. /Redux";
import Context from "./context";
/** * Connect is actually a higher-order component that mounts store data to wrappedComponent *@param {Function} mapStateToProps
 * @param {Function | Object} mapDispatchToProps
 * @returns Returns a component */
const connect =
  (mapStateToProps, mapDispatchToProps) = > (WrappedComponent) = > (props) = > {
    let stateProps = {},
      dispatchProps = {};
    const store = useContext(Context);
    const dispatch = store.dispatch;
    const subscribe = store.subscribe;

    if (typeofmapStateToProps ! = ="function") {
      throw new Error("MapStateToProps must be a function!");
    }

    if (
      typeofmapDispatchToProps ! = ="object" &&
      typeofmapDispatchToProps ! = ="function"
    ) {
      throw new Error("MapDispatchToProps must be Function or Object");
    }

    stateProps = mapStateToProps(store.getState());

    // In this function, the Dispatch method will execute the return value of Action Creator as an argument, resulting in actionCreators
    if (typeof mapDispatchToProps === "function") {
      dispatchProps = mapDispatchToProps(dispatch);
    } else if (typeof mapDispatchToProps === "object") {
      dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
    }

    const [ignore, forceUpdate] = useReducer((x) = > x + 1.0);

    // Do not use useEffect: useEffect is executed asynchronously, which may cause some data loss
    useLayoutEffect(() = > {
      const unsubscribe = subscribe(() = > {
        forceUpdate();
      });
      // Return a cleanup function
      return () = > {
        // The subscription will be deleted after the view is uninstalled
        if(unsubscribe) { unsubscribe(); }}; }, [subscribe]);return <WrappedComponent {. props} {. stateProps} {. dispatchProps} / >;
  };
export default connect;

Copy the code

useStore

UseStore is very simple

// hooks.js
import Context from './Context';
function useStore() {
    return useContext(Context);
}
Copy the code

useDispatch

UseDispatch is also relatively simple. You can extract and return the Dispatch based on useStore

// hooks.js

export function useDispatch() {
  const { dispatch } = useStore();
  return dispatch;
}
Copy the code

useSelector

  • ).
// hooks.js
export function useSelector(selector) {
  const store = useStore();
  const { getState } = store;
  const selectedState = selector(getState());

  return selectedState;
}

// function component
import { useStore, useDispatch, useSelector } from ".. /.. /React-Redux";
export default function FunctionReactRedux() {
  const store = useStore();
  const dispatch = useDispatch();
  console.log('store', store);
  const handleAdd = () = > {
    dispatch({ type: 'ADD'})}const count = useSelector(state= > state.count);
  return <div>function - Children - {count} <button onClick={handleAdd}>increase</button></div>
}
Copy the code

The original version of Selector had the same problem as the original version of Connect in that it couldn’t trigger an update to the view. Use the same solution

  • Optimized version
export function useSelector(selector) {
  const store = useStore();
  const { getState, subscribe } = store;
  const selectedState = selector(getState());

  const [ignore, forceUpdate] = useReducer(x= > x+1.0);

  // Do not use useEffect: useEffect is executed asynchronously, which may cause some data loss
    useLayoutEffect(() = > {
    const unsubscribe = subscribe(() = > {
      forceUpdate(); // Actually triggers a view update for Dispatch
    });
    return () = > {
      // The subscription will be deleted after the view is uninstalled
      if (unsubscribe) {
        unsubscribe();
      }
    }
  }, [subscribe]);

  return selectedState;
}
Copy the code