The principle of connect and provider of react Redux

Time:2020-9-28

React Redux provides connect and provider to connect react and redux.

  • Connect: used to create a container component, which enablesContainer componentsAccess the store provided by the provider component through context, and pass the state and dispatch returned by mapstatetoprops and mapdispatchtoprops to the UI component.
  • Provider: provide store to sub components through context

1. Use of connect and provider

// App.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import createStore from 'redux'
import reducer from './reducers'
import Container from './Container'

const store = createStore(reducer)
const App = () => {
    return (
        <Provider store={store}>
            <Container />
        </Provider>
    )
}

render(<App />, document.getElementById('app'))

Container components

// Container.jsx
import React from 'react'
import { connect } from 'react-redux'

const mapStateToProps = (state, ownProps) => ({})

const mapDispatchToProps = (dispatch, ownProps) => ({})

export default connect(mapStateToProps, mapDispatchToProps)(Demo)

2. Source code analysis

Let’s take a look at the directory structure of the react Redux package. The ES directory is suitable for importing es modules, and lib is suitable for importing commonjs modules
The principle of connect and provider of react Redux

2.1 source code analysis of provider

The provider component is in the Provider.js There are only dozens of lines of code defined in it. The core code is as follows:

import { ReactReduxContext } from './Context';

function Provider(_ref) {
  var store = _ ref.store , // get the store of component binding
      context = _ref.context,
      children = _ ref.children ; // get sub components
  //The value of contextvalue is {store, subscription}
  var contextValue = useMemo(function () {
    var subscription = new Subscription(store);
    subscription.onStateChange = subscription.notifyNestedSubs;
    return {
      store: store,
      subscription: subscription
    };
  }, [store]);
  var previousState = useMemo(function () {
    return store.getState();
  }, [store]);
  useEffect(function () {
    var subscription = contextValue.subscription;
    subscription.trySubscribe();

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs();
    }

    return function () {
      subscription.tryUnsubscribe();
      subscription.onStateChange = null;
    };
  }, [contextValue, previousState]);
  //If the context is bound to the provider component, the bound context is used. If the context is not bound, the context will be generated by itself
  //Children are sub components nested in the provider
  var Context = context || ReactReduxContext;
  return React.createElement(Context.Provider, {
    value: contextValue
  }, children);
}

export default Provider;

The usememo hook function is used in the source code. Only when the second parameter changes, the first parameter function will be executed, which can improve the code execution performance and avoid executing the function every time the component rendering. For details, please go to the official website, where you can make a brief introduction.

var Context = context || ReactReduxContext;
return React.createElement(Context.Provider, {
    value: contextValue
}, children);

Let’s look at this part of the code. If context is bound to the provider component, the bound context is used. If no context is bound, the context will be generated by itself. Reactireduxcontext is generated in Context.js Chinese:

import React from 'react';
export var ReactReduxContext =
/*#__PURE__*/
React.createContext(null);

if (process.env.NODE_ENV !== 'production') {
  ReactReduxContext.displayName = 'ReactRedux';
}

export default ReactReduxContext;

With context, you can provide a store to a child component.

<Provider store={store}>
    <Container />
</Provider>
//Equivalent to
<Provider store={store}>
    <Context.Provider value={{value: contextValue}}>
        <Container />
    </Context.Provider>
</Provider>

Open react devtool to see that the outermost component is < provider >, and the sub components in the inner layer are composed of< ReactRedux.Provider >Package package
The principle of connect and provider of react Redux

2.2. Connect source code analysis

Connect is used as follows:

connect(mapStateToProps, mapDispatchToProps)(Demo)

You can guessconnect(mapStateToProps, mapDispatchToProps)This part will return a high-level component, which is used to pass the state returned by mapstatetoprops and the dispatch returned by mapdispatchtoprops to demo through props. We use the source code to verify whether the conjecture is correct.

Connect function in connect.js The function shelf is as follows:

export function createConnect(_temp) {
  // coding...
  return function connect(mapStateToProps, mapDispatchToProps, mergeProps, _ref2) {
    // coding...
    return connectHOC(selectorFactory, options);
  };
}
export default createConnect();

The connecthoc function returns a high-order component wrapwithconnect (wrappedcomponent) in the connectAdvanced.js The function connectadvanced is called connecthoc.

export default function connectAdvanced(selectorFactory, _ref) {
  // coding...
  return function wrapWithConnect(WrappedComponent) {
    // coding...
    function createChildSelector(store) {
      return selectorFactory(store.dispatch, selectorFactoryOptions);
    }
    // coding...
    function ConnectFunction(props) {
      // coding...
      
      //Get context object
      var ContextToUse = useMemo(function () {
        return propsContext && propsContext.Consumer && isContextConsumer(React.createElement(propsContext.Consumer, null)) ? propsContext : Context;
      }, [propsContext, Context]); 
      
      //Get Context.Provider Bound value {store, subscription}
      var contextValue = useContext(ContextToUse);
      
      //Get store
      var store = didStoreComeFromProps ? props.store : contextValue.store;
      //Childpropsselector returns a function () that accepts store.getState () and props
      var childPropsSelector = useMemo(function () {
        return createChildSelector(store);
      }, [store]);
      
      //The child props selector is executed here, and the store.getState () and props are passed in, and mapstatetoprops receives state and props. As for dispatch, selectorfactory is executed( store.dispatch , selectorfactory options); it's passed in.
      var actualChildProps = usePureOnlyMemo(function () {
        if (childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current) {
          return childPropsFromStoreUpdate.current;
        }
        return childPropsSelector(store.getState(), wrapperProps);
      }, [store, previousStateUpdateResult, wrapperProps]);
      
      //The actual childprops get the state returned by mapstatetoprops, and put it in props and pass it to the UI component
      var renderedWrappedComponent = useMemo(function () {
        return React.createElement(WrappedComponent, _extends({}, actualChildProps, {
          ref: forwardedRef
        }));
      }, [forwardedRef, WrappedComponent, actualChildProps]);
      
      
      var renderedChild = useMemo(function () {
        //Shouldhandlestatechanges controls whether state changes in the Redux store should be subscribed to.
        if (shouldHandleStateChanges) {
          //Subscribe to the state change in Redux store and return ContextToUse.Provider Nested components
          return React.createElement(ContextToUse.Provider, {
            value: overriddenContextValue
          }, renderedWrappedComponent);
        }
        //The UI component is returned directly without subscribing to the state changes in the Redux store
        return renderedWrappedComponent;
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
      return renderedChild;
    }
    // React.memo Used to create a pure function component, the same as purecomponent, but React.memo It acts on function component and purecomponent acts on class component. The biggest effect of using pure function components is that the components will be re rendered only when props change, which can improve the rendering performance.
    var Connect = pure ? React.memo(ConnectFunction) : ConnectFunction;
    Connect.WrappedComponent = WrappedComponent;
    Connect.displayName = displayName;

    if (forwardRef) {
      var forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
        return React.createElement(Connect, _extends({}, props, {
          forwardedRef: ref
        }));
      });
      forwarded.displayName = displayName;
      forwarded.WrappedComponent = WrappedComponent;
      return hoistStatics(forwarded, WrappedComponent);
    }
    //Hoisttatics is an export of the hoist non react statistics package, which is used to copy static methods that are not included in react in a component to another component. This package is generally used to define the hoc, because when you add a hoc to a component, the original component will be wrapped by a container component, which means that the new component will not have any static methods of the original component. reference resources: https://zhuanlan.zhihu.com/p/36178509
    return hoistStatics(Connect, WrappedComponent);
  };
}

connectHOC(selectorFactory, options)inselectorFactoryFunction passed toconnectAdvanced(selectorFactory, _ref)In, inConnectFunction(props)Call in function componentcreateChildSelector(store)And then callselectorFactory(store.dispatch, selectorFactoryOptions);The selectorfactory function is the core API in connect selectorFactory.js In the file, selectorfactory is the following export.

export default function finalPropsSelectorFactory(dispatch, _ref2) {
  var initMapStateToProps = _ref2.initMapStateToProps,
      initMapDispatchToProps = _ref2.initMapDispatchToProps,
      initMergeProps = _ref2.initMergeProps,
      options = _objectWithoutPropertiesLoose(_ref2, ["initMapStateToProps", "initMapDispatchToProps", "initMergeProps"]);

  var mapStateToProps = initMapStateToProps(dispatch, options);
  var mapDispatchToProps = initMapDispatchToProps(dispatch, options);
  var mergeProps = initMergeProps(dispatch, options);

  if (process.env.NODE_ENV !== 'production') {
    verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName);
  }

  var selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory;
  // 
  return selectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options);
}

Purefinalpropsselectorfactory function implementation:

export function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, _ref) {
  var areStatesEqual = _ref.areStatesEqual,
      areOwnPropsEqual = _ref.areOwnPropsEqual,
      areStatePropsEqual = _ref.areStatePropsEqual;
  var hasRunAtLeastOnce = false;
  var state;
  var ownProps;
  var stateProps;
  var dispatchProps;
  var mergedProps;

  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState;
    ownProps = firstOwnProps;
    stateProps = mapStateToProps(state, ownProps);
    dispatchProps = mapDispatchToProps(dispatch, ownProps);
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    hasRunAtLeastOnce = true;
    return mergedProps;
  }

  function handleNewPropsAndNewState() {}

  function handleNewProps() {}

  function handleNewState() {}

  function handleSubsequentCalls(nextState, nextOwnProps) {
  // coding...
  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps);
  };
}

The function of selectorfactory is to transfer the state and props obtained from the connectfunction component of the connected store to mapstatetoprops, and the obtained dispatch to mapdispatchtoprops. The return values of mapstatetoprops and mapdispatchtoprops are then passed to the UI component using props in the connectfunction component.

Wrapwithconnect (wrapped component) returns a new connect function (props) function component connected to the store. The component will determine whether to listen to Redux according to the shouldhandlestatechanges field Change of the state in the store, return if necessary ContextToUse.Provider The child components that wrap the UI component, ContextToUse.Provider Provide the reconstructed overridden contextvalue for the group component. If you do not need to monitor the change of state in the Redux store, return the UI component as a sub component. As shown in the first part of the content example, brother component does not need state, sister component needs state, then sister component will use ContextToUse.Provider It’s wrapped. The whole component architecture will look like this:

The principle of connect and provider of react Redux

Memo indicates that the component is a pure function component

These three articles are worth reading
https://github.com/MrErHu/blo…
https://juejin.im/post/59772a…
https://segmentfault.com/a/11…