Look at React-Redux source with questions

Keywords: Javascript React Attribute

Welcome to the Public Number: One front end at a time, sharing what I understand on an irregular basis

Write before

While I was reading React-Redux source code, it was natural for me to go online and find some reference articles, but I found that none of them were very thorough.
Many times the API is told one by one in a flat, straight-line way, and only what a line of code does, without a clear explanation of why it does so in combination with scenarios and usage, and the source code itself is abstract.
The call relationships between functions are not very well organized, and the end result is that the more you look at them, the more confused they become.This time I'll try a different interpretation, starting with the most common usage.
Combine usage, ask questions, and take questions to see how they are implemented in the source code so that you can work with everyone to gradually clarify the operating mechanism of React-Redux.

The article took more than a week to finish, after a rough look at the source code, and then read and write.There are not too few sources. I try to sort out the structure in the most understandable way and try to explain the principles in a simple way.
However, the complexity of the code structure cannot be overwhelmed, and many places still need time to think about how functions are called and how they are used.The article is a bit long, you can see the last one is true love~

Limited level, inevitably there are inadequate explanations or errors, and we hope you can help point them out. Thank you.

Application of React-Redux in Project

Here, I'm going to default that you're already using Redux, which provides a global object (store) to manage state for our applications.
So how do you apply Redux to React?Consider that our ultimate goal is to achieve unified management of communication and state across components at different levels.So you can use the Context feature.

  • Create a Provider and pass the store into the Provider as the value of the current context so that the component can get the Redux store from the context
  • store subscribes to the unified logic for component updates
  • When a component needs to update its data, it needs to call store.dispatch to dispatch the action, which triggers the update of the subscription
  • Use store.getState() to get data when the component gets data

And all this needs to be done manually by yourself, React-Redux encapsulates the top.Let's take a look at the use of React-Redux in a piece of code:

First, on the outermost application of React, package the Provider, which is the component provided by React-Redux, and what you do here is the equivalent of the first step above

import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
const reducer = (state, actions) => {
  ...
}
const store = createStore(reducer)
...

class RootApp extends React.Component {
  render() {
   // store is passed into Provider here
    return <Provider store={store}>
      <App/>
    </Provider>
  }
}

The subscription in Step 2 has been implemented in Provider and connect, respectively

Look at the subcomponents in the application.If you need to pick up data from the store or update the store data (equivalent to steps 3 and 4 above),
The components need to be wrapped in a connect ion:

import React from 'react'
import { connect } from '../../react-redux-src'
import { increaseAction, decreaseAction } from '../../actions/counter'
import { Button } from 'antd'
class Child extends React.Component {
  render() {
    const { increaseAction, decreaseAction, num } = this.props
    return <div>
        {num}
        <Button onClick={() => increaseAction()}>increase</Button>
        <Button onClick={() => decreaseAction()}>reduce</Button>
    </div>
  }
}
const mapStateToProps = (state, ownProps) => {
  const { counter } = state
  return {
    num: counter.num
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    increaseAction: () => dispatch({
      type: INCREASE
    }),
    decreaseAction: () => dispatch({
      type: DECREASE
    })
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(Child)

The mapStateToProps is used to establish a mapping relationship between the component and the state stored in the store. It is a function. The first parameter is state, which is the top level data stored in redux, and the second parameter is props of the component itself.Returns an object whose field is the value that the component needs to get from the store.

MappDispatchToProps is used to establish a mapping relationship between components and store.dispatch.It can be an object or a function.
When it is a function, the first parameter is dispatch, and the second parameter is the props of the component itself.

The object form of mapDispatchToProps is as follows:

const mapDispatchToProps = {
    increaseAction() {
      return dispatch => dispatch({
        type: INCREASE
      })
    },
    decreaseAction() {
      return dispatch => dispatch({
        type: DECREASE
      })
    }
  }

When mapStateToProps is not passed, it does not cause component UI updates when store s change.

When mapDispatchToProps is not passed, dispatch is injected into the props of the component by default.

Above, if mapStateToProps or mapDispatchToProps passed ownProps, both functions will be called when the props of the component itself change.

What React-Redux did

Let's first draw a conclusion about what React-Redux does:

  • Provides a Subscrption class to implement logic for subscription updates
  • Provider s are provided to pass stores into providers so that lower-level components can retrieve stores from context s or props; changes to stores are subscribed to so that providers themselves can be updated as stores change
  • Provides a selector that takes functions (or directly dispatch es) of stat and dispacth action s in the store or the component's own props and selects from them the values required by the component as return values
  • There are two main things you can do to provide high-level connect s components:

    • Execute selector, get values to inject into the component, and inject them into the props of the component
    • Subscribe to changes in props, responsible for updating components as props change

How to do it

With the above conclusion, but I think everyone is curious about how to achieve it. The above work is done in collaboration. The final appearance is reflected in the following issues:

  • How Provider puts store s into context s
  • How to inject a state and dispatch (or a function that calls dispatch) from the store into the props of a component
  • We all know that in Redux, store changes and UI updates can be achieved by subscribing to a function that updates a page through store.subscribe(), and how React-Redux changes stores and connect ed components update

Next, analyze the source code one by one with these questions.

How Provider puts store s into context s

Start with Provider components, not much code, directly up source

class Provider extends Component {
  constructor(props) {
    super(props)
    // Remove store from props
    const { store } = props
    this.notifySubscribers = this.notifySubscribers.bind(this)
    // Declare a Subscription instance.Subscriptions, listening for state changes to execute listener s, are all implemented by instances.
    const subscription = new Subscription(store)
    // Bind monitoring, notify subscribers to update pages when state changes
    subscription.onStateChange = this.notifySubscribers
    // Put store and subscription into the state, which will be used as the value of the context later
    this.state = {
      store,
      subscription
    }
    // Getting the state in the current store, as the last state, will be done after the component is mounted.
    // Update Provider component if inconsistent with store new state
    this.previousState = store.getState()
  }

  componentDidMount() {
    this._isMounted = true

    // Subscribe to updates after the component has been mounted.As for how to subscribe, the Subscription class is described below.
    // Understand that you need to subscribe to the update function initially to update the Provider component when the state changes
    this.state.subscription.trySubscribe()

    // If the state s in the store s before and after have changed, then go and update the Provider component
    if (this.previousState !== this.props.store.getState()) {
      this.state.subscription.notifyNestedSubs()
    }
  }

  componentWillUnmount() {
    // Unsubscribe when component is uninstalled
    if (this.unsubscribe) this.unsubscribe()
    this.state.subscription.tryUnsubscribe()
    this._isMounted = false
  }

  componentDidUpdate(prevProps) {
    // When the component is updated, check to see if the current store is consistent with the previous store. If it is not, you should make changes based on the new data.
    // It is no longer meaningful to make changes to the original data, so the Subscription instance will be redeclared after unsubscribing.
    // Bind listening, set state to new data
    if (this.props.store !== prevProps.store) {
      this.state.subscription.tryUnsubscribe()
      const subscription = new Subscription(this.props.store)
      subscription.onStateChange = this.notifySubscribers
      this.setState({ store: this.props.store, subscription })
    }
  }

  notifySubscribers() {
    // notifyNestedSubs() actually notifies listener to execute, which is to update the UI
    this.state.subscription.notifyNestedSubs()
  }

  render() {
    const Context = this.props.context || ReactReduxContext
    // Pass this.state as the value of the context
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
  }
}

So look at this in conjunction with the code: It's easy to understand how Provider puts store s into context s.
The main function of Provider is to get the store we passed in from props and send it down to the lower components as one of the values of the context.

However, once the store changes, the Provider has to react to it to ensure that the latest store is always in the context.So subscriptions are used here to update.The Subscription class is naturally introduced, and through its instances onStateChange listens for an event that updates the UI
On this.notifySubscribers:

subscription.onStateChange = this.notifySubscribers

After the component is mounted, subscribe to the update, and what you subscribe to here depends on the implementation of Subscription.The first conclusion here is that the onStateChange subscription is essentially an onStateChange subscription, and the function that implements the subscription is trySubscribe within the Subscription class

this.state.subscription.trySubscribe()

Then, if the States before and after are different, go and notify the subscriber to update, onStateChange will be executed, and the Provider component will be updated.When the update is complete (componentDidUpdate),
It compares whether the stores before and after are the same, and if they are different, uses the new store as the value of the context, unsubscribes and re-subscribes to a new Subscription instance.The data guaranteed to be up to date.

So with all this said, it's actually just an update of the Provider component, not an internal update mechanism of a connect ed component.I guess there should be one reason to think about the potential for providers to be nested, so there is this practice of retrieving data and re-subscribing after providers are updated so that the context s passed to subcomponents are up-to-date.

Subscription

We have found that the Provider component is updated by a method in the Subscription class, and that the update of the connect higher-order component, which we will talk about later, is also implemented by it. Subscription is the core mechanism for React-Redux to implement subscription updates.

import { getBatch } from './batch'
const CLEARED = null
const nullListeners = { notify() {} }

function createListenerCollection() {
  const batch = getBatch()
  let current = []
  let next = []

  return {
    clear() {
      // Empty next and current
      next = CLEARED
      current = CLEARED
    },

    notify() {
      // Assign next to current and listeners, where next, current, listeners are actually queues of subscribed update functions
      const listeners = (current = next)
      // Batch listeners
      batch(() => {
        for (let i = 0; i < listeners.length; i++) {
        // Perform update functions, which are the most fundamental principle for triggering UI updates
          listeners[i]()
        }
      })
    },

    get() {
      return next
    },

    subscribe(listener) {
      let isSubscribed = true
      // Copy the current and assign it to next, then push listener to next
      if (next === current) next = current.slice()
      next.push(listener)

      return function unsubscribe() {
        if (!isSubscribed || current === CLEARED) return
        isSubscribed = false
        // Ultimately, a unsubscribe function is returned to clean up useless listener s on the next round
        if (next === current) next = current.slice()
        next.splice(next.indexOf(listener), 1)
      }
    }
  }
}

export default class Subscription {
  constructor(store, parentSub) {
    // Get a store to subscribe through
    this.store = store
    // Get a subscription instance from the parent, mainly one you might use when connect ing
    this.parentSub = parentSub
    this.unsubscribe = null
    this.listeners = nullListeners

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
  }

  addNestedSub(listener) {
    this.trySubscribe()
    // Since this is called by parentSubs, listener s are also subscribed to parentSubs, which is the subscription obtained from the Provider
    return this.listeners.subscribe(listener)
  }

  notifyNestedSubs() {
    // Notify listeners to execute
    this.listeners.notify()
  }

  handleChangeWrapper() {
    if (this.onStateChange) {
      // onStateChange is assigned a different update function when it is externally instantiated as a subcription instance, in Provider and connect, respectively.
      // Since the function you just subscribed to is handleChangeWrapper, it is also equivalent to listener.So when the state changes, listener executes, onStateChange executes
      this.onStateChange()
    }
  }

  isSubscribed() {
    return Boolean(this.unsubscribe)
  }

  trySubscribe() {
    if (!this.unsubscribe) {
      // ParentSubis actually a subcription instance
      // This judges the value of this.unsubscribe after it has been assigned, which essentially means whether parentSub is there, and assigns it to this.unsubscribe by the way.
      // If parentSub is not passed, use store subscription, otherwise call parentSub.addNestedSub and use React-Redux's own subscription logic.This is explained in the explanation below the code
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : this.store.subscribe(this.handleChangeWrapper)
      // Create a listener collection
      this.listeners = createListenerCollection()
    }
  }

  tryUnsubscribe() {
    // unsubscribe
    if (this.unsubscribe) {
      this.unsubscribe()
      this.unsubscribe = null
      this.listeners.clear()
      this.listeners = nullListeners
    }
  }
}

Subscription links the work of updating a page to changes in its state, specifically listener (the method that triggers page updates, in this case handleChangeWrapper), which, through the trySubscribe method, is subscribed to within the store or Subscription as appropriate.Place in the listeners array, and when the state changes, the listeners loop executes each listener, triggering page updates.

Let's talk about whether trySubscribe uses store subscriptions directly or calls addNestedSub to implement internal subscriptions, depending on the circumstances.Because there may be multiple stores in an application, the judgement here is to have different stores subscribe to their listener s without interruption.

How to inject state and dispatch into components

After injecting store s from the top level of the application, it's time to consider how to inject States and dispatch es into the component.

The normal order must be to get to the store first, then execute the two functions separately in some way, passing in the state and dispatch in the store, along with the props of the component itself as parameters to mapStateToProps and mapDispatchToProps, so that we can get these values in both functions.Their return values are then injected into the props of the component.

Speaking of this, you have to come up with a concept: selector.The props that are eventually injected into the component are the return values of selectors generated by the selectorFactory function, so that is, mapStateToProps and mapDispatchToProps are essentially selectors.

The generation process is in the core function connectAdvanced of connect, at which point you can get the store in the current context and use the store passed into the selectorFactory to generate the selector in the form of

function selector(stateOrDispatch, ownProps) {
  ...
  return props
}

It can be seen that selector is equivalent to mapStateToProps or mapDispatchToProps, and the return value of selector is injected into the component as props.

From mapToProps to selector

The Title mapToProps refers generally to mapStateToProps, mapDispatchToProps, mergeProps

In combination with everyday use, we know that our components don't get States and dispatch es until they are wrapped by connects, so with the above conclusion, we comb the selector's mechanism separately and see the source code of connects first:

export function createConnect({
  connectHOC = connectAdvanced, // ConneAdvanced function is the core of connect
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory
} = {}) {
  return function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {...options} = {}
  ) {
    // Initialize all of our incoming mapStateToProps, mapDispatchToProps, mergeProps
    const initMapStateToProps = match(mapStateToProps,
      mapStateToPropsFactories,
      'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
    // Returns the call to the connectHOC function, whose interior is the core of the connection
    return connectHOC(selectorFactory, {
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      ...
    })
  }
}

export default createConnect()

Connect is actually createConnect, and createConnect simply returns a connect function, which returns the call to connectHOC (that is, the call to connectAdvanced), and then proceeds. The call to connectAdvanced eventually returns a wrapWithConnect higher-order component, the parameter of which is what we pass inComponents of.So there's the usual use of connect:

connect(mapStateToProps, mapDispatchToProps)(Component)

You should note that mapStateToProps, mapDispatchToProps, mergeProps are all initialized once in the connect function. Why do you want to initialize them instead of using them directly?With questions, let's look down.

Initialize the selector process

First look at the code, mainly initMapStateToProps and initMapDispatchToProps, to see what this code means.

const initMapStateToProps = match(mapStateToProps,
  mapStateToPropsFactories,
  'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps,
  mapDispatchToPropsFactories,
  'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

mapStateToPropsFactories and mapDispatchToPropsFactories are arrays of functions, each of which receives a parameter, either mapStateToProps or mapDispatchToProps.The match function acts as an array of looping functions, either mapStateToProps or mapDispatchToProps being executed as an input to each function and assigned to the left when the return value of the function is not false.Take a look at the match function:

function match(arg, factories, name) {
  // Loop through factories, where factories are the array of processing functions exposed in the mapStateToProps and mapDisPatchToProps files
  for (let i = factories.length - 1; i >= 0; i--) {
    // arg is also mapStateToProps or mapDispatchToProps
    // This is equivalent to passing the Star of each function in the array and passing in our mapToProps function as a parameter
    const result = factories[i](arg)
    if (result) return result
  }
}

The match loop is an array of functions, so let's look at these two arrays, mapStateToPropsFactories and mapDispatchToPropsFactories:
(The whenMapStateToPropsIsFunction function in the lower source code is explained later)

  • mapStateToPropsFactories

    • import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
      // Call wrapMapToPropsFunc when mapStateToProps is a function
      export function whenMapStateToPropsIsFunction(mapStateToProps) {
        return typeof mapStateToProps === 'function'
          ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
          : undefined
      }
      // Call wrapMapToPropsConstant when mapStateToProps is not passed
      export function whenMapStateToPropsIsMissing(mapStateToProps) {
        return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
      }
      
      export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]

      In fact, let both whenMapStateToPropsIsFunction and whenMapStateToPropsIsMissing execute mapStateToProps once, and then choose the function with the result of execution to assign to initMapStateToProps based on the situation of the incoming mapStateToProps.

      Take a separate look at whenMapStateToPropsIsMissing

      export function wrapMapToPropsConstant(getConstant) {
        return function initConstantSelector(dispatch, options) {
          const constant = getConstant(dispatch, options)
          function constantSelector() {
            return constant
          }
          constantSelector.dependsOnOwnProps = false
          return constantSelector
        }
      }

      wrapMapToPropsConstant returns a function that receives the parameter we passed in () => ({}), which calls the parametric function internally and assigns a constant to the constantSelector.
      This constant is essentially the selector generated when we do not pass mapStateToProps, which returns an empty object and therefore does not accept any state from the store.You can also see constantSelector.dependsOnOwnProps = false, indicating that the return value is independent of the props received by the connect or higher-order component.

  • mapDispatchToPropsFactories

    •     import { bindActionCreators } from '../../redux-src'
          import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
      
          export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
            return typeof mapDispatchToProps === 'function'
              ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
              : undefined
          }
          // When mapDispatchToProps is not passed, dispatch is injected into the component by default
          export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
            return !mapDispatchToProps
              ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
              : undefined
          }
          // When the incoming mapDispatchToProps is an object, use bindActionCreators for processing as detailed in redux/bindActionCreators.js
          export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
            return mapDispatchToProps && typeof mapDispatchToProps === 'object'
              ? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
              : undefined
          }
      
          export default [
            whenMapDispatchToPropsIsFunction,
            whenMapDispatchToPropsIsMissing,
            whenMapDispatchToPropsIsObject
          ]

      When mapDispatchToProps is not passed, when MapDispatchToPropsIsMissing is called, constantSelector returns only one dispatch, so dispatches can only be received in the component.

      When the incoming mapDispatchToProps is an object, it also calls wrapMapToPropsConstant, where, according to the previous understanding, the properties injected into the component are
      The result of execution of bindActionCreators(mapDispatchToProps, dispatch).

Now let's look at the whenMapStateToPropsIsFunction function.It is called when both mapDispatchToProps and mapStateToProps are functions and the implementation is complex.Only mapStateToProps is used to illustrate this.

Reminder: the following mapToProps refer to mapDispatchToProps or mapStateToProps

// Determine whether a component should depend on its props based on the number of arguments to the mapStateToProps function
export function getDependsOnOwnProps(mapToProps) {
  return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

export function wrapMapToPropsFunc(mapToProps, methodName) {
  // The final wrapMapToPropsFunc returns a proxy function, which is returned in the selectorFactory function
  // Is called within finalPropsSelectorFactory and assigned to another variable.
  // This proxy function is executed in the selectorFactory to generate the final selector
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      // Depending on whether the component relies on its props, it decides what parameters to pass when calling
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }
    proxy.dependsOnOwnProps = true
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      // Assign proxy.mapToProps to the mapToProps we passed in
      proxy.mapToProps = mapToProps
      // Determines whether ownProps need to be injected into a component based on whether the component passes in props that the component itself receives from the parent component.
      // Ultimately it will be used to implement the props change of the component itself, and it will also call the effect of mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      // Go ahead and execute proxy, when proxy.mapToProps has been assigned to the mapToProps function that we passed in,
      // So props are assigned to the return value of the incoming mapToProps
      let props = proxy(stateOrDispatch, ownProps)
      if (typeof props === 'function') {
        // If the return value is a function, then execute the function and pass in the state or dispatch in the store, as well as ownProps
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }
      if (process.env.NODE_ENV !== 'production')
        verifyPlainObject(props, displayName, methodName)
      return props
    }
    return proxy
  }
}

WapMapToPropsFunc actually returns the initProxySelector function, and the execution result of initProxySelector is a proxy, which can be understood as proxying incoming data (state or dispatch, ownProps) to our incoming mapToProps function.The result of proxy execution is proxy.mapToProps, which is essentially selector.

When the page initialization executes, dependsOnOwnProps is true, so proxy.mapToProps(stateOrDispatch, ownProps), also known as detectFactoryAndVerify, is executed.In subsequent executions, the proxy's mapToProps are assigned to the mapStateToProps or mapDispatchToProps we pass in to connect, and then whether the component should depend on its own props is assigned to dependsOnOwnProps as appropriate.(Note that this variable is used in the selectorFactory function as a basis for whether the component performs the mapToProps function based on its own props changes).

To summarize, what this function essentially does is hang the mapToProps function that we pass in to connect onto proxy.mapToProps, and then mount a dependsOnOwnProps on the proxy to make it easier to distinguish whether a component depends on its own props.Finally, the proxy is also used as the return value of the initProxySelector, so the initialization process is actually referenced by the functions of the initMapStateToProps, initMapDispatchToProps, initMergeProps assigned to them, which are executed followed by the proxy, as to where their three proxys are executed to generate a specific selector for meYou'll see below.

Now, recall our question, why do you want to initialize those three mapToProps functions?The goal is obviously to prepare the function that generates the selector to execute it at the right time and decide whether the selector should respond to changes in ownProps.

Create a selector and inject props into the component

Once you're ready to generate the selector function, you need to execute it and inject its return value into the component as props.First, give a brief overview of the injection process:

  • Get the store's state or dispatch, and ownProps
  • Execute selector
  • Inject the returned value of execution into the component

Next, we need to go backwards from the last step of injection to see how the selector performs.

The injection process occurs in the core function connectAdvanced of connect. Ignore other processes in the function, focus on the injection process, and simply look at the source code

export default function connectAdvanced(
  selectorFactory,
  {
    getDisplayName = name => `ConnectAdvanced(${name})`,
    methodName = 'connectAdvanced',
    renderCountProp = undefined,
    shouldHandleStateChanges = true,
    storeKey = 'store',
    withRef = false,
    forwardRef = false,
    context = ReactReduxContext,
    ...connectOptions
  } = {}
) {
  const Context = context
  return function wrapWithConnect(WrappedComponent) {

    // ...other code ignored

    // selectorFactoryOptions is a series of parameters that contain our initialized mapToProps
    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }
    // pure means that the selector is recalculated only when the state or ownProps changes.
    const { pure } = connectOptions

    /* createChildSelector Called as createChildSelector(store)(state, ownProps),
       createChildSelector A call to selectorFactory is returned, and selectorFactory is actually returned internally according to options.pure
       impureFinalPropsSelectorFactory Or a call to pureFinalPropsSelectorFactory, which requires parameters such as
           mapStateToProps,
           mapDispatchToProps,
           mergeProps,
           dispatch,
           options
       All parameters except dispatch are available from selectorFactoryOptions.The return value of the call is selector.The parameters required by selector are
       (state, ownprops). So we conclude that createChildSelector(store) is a selector
    */
    function createChildSelector(store) {
      // Here is the call to finalPropsSelectorFactory in selectorFactory.js (essentially the call to mapToProps we initialized above), passing in dispatch, and options
      return selectorFactory(store.dispatch, selectorFactoryOptions)
    }

    function ConnectFunction(props) {
      const store = props.store || contextValue.store
      // Create selector only when store changes
      // Call childPropsSelector => childPropsSelector (dispatch, options)
      const childPropsSelector = useMemo(() => {
        // Recreate this selector whenever the store changes
        return createChildSelector(store)
      }, [store])

      // ActlChildProps is the props that are ultimately injected into the component, which is the return value of the selector.
      const actualChildProps = usePureOnlyMemo(() => {
        return childPropsSelector(store.getState(), wrapperProps)
      }, [store, previousStateUpdateResult, wrapperProps])

      const renderedWrappedComponent = useMemo(
        // This is where props are injected into the component
        () => <WrappedComponent {...actualChildProps} />,
        [forwardedRef, WrappedComponent, actualChildProps]
      )
    }
  // Last return Out
  return hoistStatics(Connect, WrappedComponent)
}

There is one important thing during the injection process: the selectorFactory.This function is an important part of generating selectors.It serves as an upload and download function, passing the received dispatch, along with the three mapToProps functions, into the processing function (pureFinalPropsSelectorFactory or impureFinalPropsSelectorFactory) inside the selectorFactory, whose execution result is a call to the internal processing function.The result of the internal processing function is that the three selectors (mapStateToProps, mapDispatchToProps, mergeProps)
The result of the merge after execution.This is the props that will eventually be passed to the component

Let's look at the internal implementation of selectorFactory.For clarity, just look at the internal structure

// Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps directly as return values
export function impureFinalPropsSelectorFactory(){}

export function pureFinalPropsSelectorFactory() {
  // Called the first time the entire process is initialized
  function handleFirstCall(firstState, firstOwnProps) {}

  // Return to new props
  function handleNewPropsAndNewState() {
    // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values
  }

  // Return to new props
  function handleNewProps() {
    // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values
  }

  // Return to new props
  function handleNewState() {
    // Merge the execution results of mapStateToProps, mapDispatchToProps, ownProps as return values
  }

  // Subsequent process calls
  function handleSubsequentCalls(nextState, nextOwnProps) {}

  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    // The first rendering, calls handleFirstCall, and subsequent action distributions trigger handleSubsequentCalls
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
}

// The finalPropsSelectorFactory function is a selectorFactory function that is called within the connectAdvaced function
export default function finalPropsSelectorFactory(
  dispatch,
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  const mapStateToProps = initMapStateToProps(dispatch, options)
  // Here is the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js, a modification
  // Later, mapStateToProps will be called again within the function returned below
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  const mergeProps = initMergeProps(dispatch, options)
  // Depending on whether the pure property is passed in, decide which function to call to generate the selector to calculate the props passed to the component.And assign the matched function to selectorFactory
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory // Recalculate props when props or state changes
    : impureFinalPropsSelectorFactory // Recalculate props directly

  // Returns a call to selectorFactory
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

It can be seen that the selectorFactory internally determines when a new props will be generated.Let's take a look at the complete source code

export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  // If this function is called, the execution results of the three selector s are merged back directly
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    )
  }
}

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  // Save a variable with a closure to mark whether it is the first execution
  let hasRunAtLeastOnce = false
  // The lower variables are used to cache the calculation results
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps

  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState
    ownProps = firstOwnProps
    // Here is the call to the proxy function inside the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js.
    stateProps = mapStateToProps(state, ownProps)
    /*
    * Your knee is rotten, it's too wrapped
    * Review proxy:
    *   const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {}
    *   return proxy
    * */
    dispatchProps = mapDispatchToProps(dispatch, ownProps)
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    hasRunAtLeastOnce = true
    // Return calculated props
    return mergedProps
  }

  function handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state, ownProps)
    // Since the call conditions for this function are ownProps and state, it is necessary to judge dependsOnOwnProps
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewProps() {
    // Decide to recalculate stateProps if you need to rely on the component's own props
    if (mapStateToProps.dependsOnOwnProps) {
      stateProps = mapStateToProps(state, ownProps)
    }
    // Ditto
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)
    // Integrate the component's own props, dispatchProps, stateProps
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
    stateProps = nextStateProps
    // Since handleNewState executes on the premise that pure is true, it is necessary to determine whether the state from the store has changed before or after
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleSubsequentCalls(nextState, nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    const stateChanged = !areStatesEqual(nextState, state)
    state = nextState
    ownProps = nextOwnProps
    // Depending on the situation, call different functions
    if (propsChanged && stateChanged) return handleNewPropsAndNewState()    // Call handleNewPropsAndNewState() to get the latest props when the component's own props and some state s in the injected store change simultaneously
    if (propsChanged) return handleNewProps() // Call handleNewProps to get the latest props only when the component's own props change, where props include injected props, the component's own props, and functions within dpspatch
    if (stateChanged) return handleNewState() // Call handleNewState() to get the latest props only if some state in the injected store changes, where props include injected props, props of the component itself, and functions within dpspatch
    // If it does not change, go directly back to the previously cached mergedProps, and in all three functions, the data is cached using closure mechanisms
    return mergedProps
  }

  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    // The first rendering, calls handleFirstCall, and subsequent action distributions trigger handleSubsequentCalls
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
}

export default function finalPropsSelectorFactory(
  dispatch,
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  const mapStateToProps = initMapStateToProps(dispatch, options) // Here is the Curitized call to the wrapMapToPropsFunc function in wrapMapToProps.js, a modification
  // Later, mapStateToProps will be called again within the function returned below
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  const mergeProps = initMergeProps(dispatch, options)
  // Verify the mapToProps function and alert you when there are errors
  if (process.env.NODE_ENV !== 'production') {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }
  // Decide how the new props will be calculated based on whether pure is passed in, defaulting to true
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

So far, we have understood when the mapToProps function was executed.Let's review this section again: How to inject state and dispatch into the component, let's comb it out from the beginning:

Incoming mapToProps

First, when you connect, you pass in mapStateToProps, mapDispatchToProps, mergeProps.Recall the usage of functions that receive state s or dispatch es and ownProps internally, and their return values pass into the props of the component.

Generating selector based on mapToProps

The return values of these functions need to be recalculated based on ownProps, so proxy functions are generated based on these functions. The result of proxy function execution is selector. The dependsOnOwnProps property is mounted on it, so it is only when it is actually executed inside selectorFactory.Basis for when to recalculate.

Pass the results of selector execution into the component as props

This step creates a call to selectorFactory within the connectAdvanced function to pass in the store and the initialized mapToProps function and other configurations.Execute mapToProps (that is, selector) within the selectorFactory, get the returned values, and finally pass them into the component.

Be accomplished

Update mechanism for React-Redux

The React-Redux update mechanism is also a subscription publishing mode.Similar to Redux, call listener to update the page once the state changes.Let's follow this process to capture the key points:

  • Update Who?
  • What is the update function for subscriptions?
  • How can I judge the state change?

Don't look at the code in a hurry. I think it's easier to understand these key issues by describing them in words first, instead of just looking at the code in a fog.

Update Who?

Recall that when you normally use React-Redux, are only components that have been connect ed and passed into mapStateToProps responding to changes in store s?
So what's updated is the component that has been connected, and connect returns connectAdvanced, and connectAdvanced returns the component that we passed in.
Essentially, connectAdvanced updates itself internally based on changes in store s to update the real components.

What is the update function for subscriptions?
This can be easily seen when subscribing internally to connectAdvanced:

subscription.onStateChange = checkForUpdates
subscription.trySubscribe()

The function of the subscription is checkForUpdates, and what matters is what this checkForUpdates does to update the components.A reducer is built into useReducer in connectAdvanced, and what this function does is dispatch an action to trigger an update when the precondition (state change) is established.

How can I judge the state change?
This is a good question to understand, because every time redux returns a new state.Just decide if the references to the States before and after are the same.

connect Core--ConneAdvanced

ConneAdvanced is a relatively heavy higher-order function. The update mechanism is outlined above, but many of the specific practices are implemented in ConneAdvanced.The source is long, the logic is a bit complex, and I've written detailed comments.The process of viewing requires thinking about the call relationships between functions and their purposes, the meaning of each variable, and with the above conclusions, it is not difficult to understand.

// This is a library of static methods to preserve components
import hoistStatics from 'hoist-non-react-statics'
import React, {
  useContext,
  useMemo,
  useEffect,
  useLayoutEffect,
  useRef,
  useReducer
} from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import Subscription from '../utils/Subscription'

import { ReactReduxContext } from './Context'

const EMPTY_ARRAY = []
const NO_SUBSCRIPTION_ARRAY = [null, null]

// Built-in reducer
function storeStateUpdatesReducer(state, action) {
  const [, updateCount] = state
  return [action.payload, updateCount + 1]
}

const initStateUpdates = () => [null, 0]

// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
// useLayoutEffect in the browser. We need useLayoutEffect because we want
// `connect` to perform sync updates to a ref to save the latest props after
// a render is actually committed to the DOM.
// My own translation of the above English comment:
// react warns when useLayoutEffect is used in a server-side environment. To solve this problem, useEffect needs to be used on the server-side and useLayoutEffect on the browser-side.
// useLayoutEffect synchronously invokes callbacks into all DOM changes.
// So you need to use it in a browser environment, because connect will synchronize the update ref to save the latest props after rendering is committed to the DOM

// The ReactHooks document describes useLayoutEffect: The update plan inside useLayoutEffect will be refreshed synchronously before the browser performs drawing.

// useEffect's effectects will be executed after each rendering round, and useLayoutEffect's effects will be executed before drawing after the dom changes.
// What effect ect does here is update
// By the time the server renders, the page has come out, and it is possible that the js has not been loaded yet.
// So you need to use useEffect in the SSR phase to make sure that after the page is taken over by js, you update it if you need to.
// There is no such problem in the browser environment

// Determine whether it's server or browser based on whether there's a window
const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

export default function connectAdvanced(
  selectorFactory,
  // options object:
  {
    // Get the name of the component after it is wrapped by connect
    getDisplayName = name => `ConnectAdvanced(${name})`,

    // For display of error message
    methodName = 'connectAdvanced',

    // Direct translated English comment: If defined, an attribute named this value will be added to the props passed to the packaged component.Its value will be the number of times the component is rendered, which is useful for tracking unnecessary re-rendering.Default value: undefined
    renderCountProp = undefined,

    // Should the connect component respond to changes in store s
    shouldHandleStateChanges = true,

    // This is required when multiple stores are used, in order to distinguish which store to get
    storeKey = 'store',

    // If true, a reference is stored in the wrapped component instance.
    // And obtained by getWrappedInstance().
    withRef = false,
    // Used to pass ref s in
    forwardRef = false,

    // context used internally by components that users can customize
    context = ReactReduxContext,

    // The remaining configuration items, selectorFactory should use
    ...connectOptions
  } = {}
) {
  //Some error logic was omitted

  // Get context
  const Context = context

  return function wrapWithConnect(WrappedComponent) {

    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || 'Component'

    const displayName = getDisplayName(wrappedComponentName)

    // Define selectorFactoryOptions in preparation for constructing selectors
    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }
    const { pure } = connectOptions
   /* Call createChildSelector => createChildSelector (store) (state, ownProps)
     createChildSelector Returns a parameterized call to selectorFactory, which is actually returned internally according to options.pure
     impureFinalPropsSelectorFactory Or a call to pureFinalPropsSelectorFactory, which requires parameters (state, ownProps)
    */
    function createChildSelector(store) {
      // Here is the call to finalPropsSelectorFactory in selectorFactory.js, passing in dispatch, and options
      return selectorFactory(store.dispatch, selectorFactoryOptions)
    }

    // Pure means something similar to React's PureComponent here, depending on whether it is pure mode or not to determine whether updates need to be optimized
    const usePureOnlyMemo = pure ? useMemo : callback => callback()

    function ConnectFunction(props) {
      // Props changes, get the latest context,forwardedRef, and other props for components
      const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {
        const { context, forwardedRef, ...wrapperProps } = props
        return [context, forwardedRef, wrapperProps]
      }, [props])
      // The propsContext or Context changes, deciding which context to use and preferring if it exists
      const ContextToUse = useMemo(() => {
        // Users may replace ReactReduxContext with a custom context to cache which context instance we should use
        // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.
        // Memoize the check that determines which context instance we should use.
        return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
      }, [propsContext, Context])

      // Get store in context from upper components
      // Returns the current value of the context, that is, the store, when the context of the upper component has recently changed
      const contextValue = useContext(ContextToUse)
      // store must exist in prop or context
      // Determine if store is from a store in props
      const didStoreComeFromProps = Boolean(props.store)
      // Determine if the store is from a store in the context
      const didStoreComeFromContext =
        Boolean(contextValue) && Boolean(contextValue.store)

      // Remove the store from the context and prepare it for injection into the component after being processed by the selector.Prefer stores in props
      const store = props.store || contextValue.store
      // Create selector only when store changes
      // ChildPropsSelector call method: childPropsSelector(dispatch, options)
      const childPropsSelector = useMemo(() => {
        // The creation of selector depends on the incoming store
        // Recreate this selector whenever the store changes
        return createChildSelector(store)
      }, [store])

      const [subscription, notifyNestedSubs] = useMemo(() => {
        if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
        // If the store is from props, the subscription instance is no longer passed in, otherwise the subscription instance passed in from the context is used
        const subscription = new Subscription(
          store,
          didStoreComeFromProps ? null : contextValue.subscription
        )
        const notifyNestedSubs = subscription.notifyNestedSubs.bind(
          subscription
        )

        return [subscription, notifyNestedSubs]
      }, [store, didStoreComeFromProps, contextValue])
      // contextValue is the store, overwrites the store once and injects the subscription so that the connect ed component can get the subscription in the context
      const overriddenContextValue = useMemo(() => {
        if (didStoreComeFromProps) {
          // If the component is directly subscribed to a store from props, use the context from props directly
          return contextValue
        }

        // Otherwise, put this component's subscription instance into context, so that
        // connected descendants won't update until after this component is done
        // Interpretation:
        // If the store is retrieved from the context, place the subscription in the context.
        // To ensure that the subcomponents that are connect ed before the component update is complete will not be updated
        return {
          ...contextValue,
          subscription
        }
      }, [didStoreComeFromProps, contextValue, subscription])

      // Built-in reducer to update components, which is used in the checkForUpdates function as the core of the update mechanism
      const [
        [previousStateUpdateResult],
        forceComponentUpdateDispatch
      ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)

      if (previousStateUpdateResult && previousStateUpdateResult.error) {
        throw previousStateUpdateResult.error
      }

      // Set up refs to coordinate values between the subscription effect and the render logic
      /*
      * Official explanation:
      * useRef Returns a mutable ref object whose.current property is initialized as an incoming parameter (initialValue).
      * The returned ref object remains unchanged throughout the life cycle of the component.
      *
      * ref Not only for DOM, the current property of useRef() can be used to hold values, similar to the instance property of a class
      *
      * */
      const lastChildProps = useRef() // props of components, including, store, dispatch from parent
      const lastWrapperProps = useRef(wrapperProps) // The component itself comes from the props of the parent component
      const childPropsFromStoreUpdate = useRef() // Mark if props from store have been updated
      const renderIsScheduled = useRef(false) // Mark when to update
      /*
      * actualChildProps Is the props that are really going to be injected into the component
      * */
      const actualChildProps = usePureOnlyMemo(() => {
        // Tricky logic here:
        // - This render may have been triggered by a Redux store update that produced new child props
        // - However, we may have gotten new wrapper props after that
        // If we have new child props, and the same wrapper props, we know we should use the new child props as-is.
        // But, if we have new wrapper props, those might change the child props, so we have to recalculate things.
        // So, we'll use the child props from store update only if the wrapper props are the same as last time.
        /*
        * Interpretation:
        * This rendering will be triggered when an update to the store generates a new props, however, we may receive a new props from the parent component after that, if there is a new props,
        * And props from parent components are unchanged, so we should update them based on the new child props.However, updates to props from parent components can also result in changes to the overall props and have to be recalculated.
        * So only update if the new props change and the props from the parent component agree with the last one (the judgement in the code below is true)
        *
        * That is, relay only on props updates caused by store changes
        * */
        if (
          childPropsFromStoreUpdate.current &&
          wrapperProps === lastWrapperProps.current
        ) {
          return childPropsFromStoreUpdate.current
        }
        return childPropsSelector(store.getState(), wrapperProps)
      }, [store, previousStateUpdateResult, wrapperProps])
      // We need this to execute synchronously every time we re-render. However, React warns
      // about useLayoutEffect in SSR, so we try to detect environment and fall back to
      // just useEffect instead to avoid the warning, since neither will run anyway.
      /*
      * We need to synchronize this effect every time we re-render.But react will drop a warning for useLayoutEffect in the case of SSR.
      * So the end result of useIsomorphic LayoutEffect is a useEffect or useLayoutEffect judged by the environment.Use useEffect when rendering on the server side.
      * Since useEffect will wait until js takes over the page in this case, it will not warning
      * */
      /*
      * Overall, there are two useIsomorphic LayoutEffects above and below, but the difference is when they are executed.
      *
      * The first one does not pass in an array of dependencies, so effect s execute each time they are re-rendered, responsible for each re-rendered
      * When checking for changes in data from store s, listeners are notified to update
      *
      * The second depends on store, subscription, child PropsSelector.So during these three changes, execute the effect.
      * Its internal effects do something different from the first one, defining the update function checkForUpdates, subscribing to the update function, so that when the first effect responds to store updates,
      * Update functions can be executed as listener s to update the page
      *
      * */

      useIsomorphicLayoutEffect(() => {
        lastWrapperProps.current = wrapperProps // Get the props of the component itself
        lastChildProps.current = actualChildProps // Get props injected into the component
        renderIsScheduled.current = false // Indicates that the rendering phase has passed
        // If the render was from a store update, clear out that reference and cascade the subscriber update
        // If the props from the store are updated, notify listeners to execute, that is, execute the previously subscribed this.handleChangeWrapper (in the Subscription class).
        // handleChangeWrapper calls onStateChange, which is the function checkForUpdates assigned below that updates the page
        if (childPropsFromStoreUpdate.current) {
          childPropsFromStoreUpdate.current = null
          notifyNestedSubs()
        }
      })

      // Our re-subscribe logic only runs when the store/subscription setup changes
      // Resubscription is only executed when subscription changes within the store.These two changes mean re-subscribing, since guaranteeing the latest data is delivered, previous subscriptions are no longer meaningful
      useIsomorphicLayoutEffect(() => {
        // If there is no subscription, return directly, shouldHandleStateChanges defaults to true, so the default will continue
        if (!shouldHandleStateChanges) return

        // Capture values for checking if and when this component unmounts
        // When a component is uninstalled, use a closure to declare whether two variable markers have been unsubscribed and the error object
        let didUnsubscribe = false
        let lastThrownError = null

        // When the store or subscription changes, the callback is re-executed, enabling re-subscription
        const checkForUpdates = () => {
          if (didUnsubscribe) {
            // If you cancel your subscription, do nothing
            return
          }
          // Get the latest state
          const latestStoreState = store.getState()

          let newChildProps, error
          try {
            // Use selector to get the latest props
            newChildProps = childPropsSelector(
              latestStoreState,
              lastWrapperProps.current
            )
          } catch (e) {
            error = e
            lastThrownError = e
          }

          if (!error) {
            lastThrownError = null
          }

          // If props do not change, just notify listeners of updates
          if (newChildProps === lastChildProps.current) {
            /*
            * In a browser environment, useLayoutEffect is executed after the DOM changes and before drawing.
            * Since the useIsomorphicLayoutEffect above executes at this time setting renderIsScheduled.current to false,
            * So go inside and make sure you trigger the update at the right time
            *
            * */
            if (!renderIsScheduled.current) {
              notifyNestedSubs()
            }
          } else {
            /*
            * If props change, cache the new props and set childPropsFromStoreUpdate.current to the new props to facilitate the first
            * useIsomorphicLayoutEffect When executed, it is recognized that props are indeed updated
            * */
            lastChildProps.current = newChildProps
            childPropsFromStoreUpdate.current = newChildProps
            renderIsScheduled.current = true
            // When dispatch has built-in action s, the ConnectFunction component is updated to update the component
            forceComponentUpdateDispatch({
              type: 'STORE_UPDATED',
              payload: {
                latestStoreState,
                error
              }
            })
          }
        }

        // The role of onStateChange is also listener.In the provider, assign a value to update listeners.Assign checkForUpdates to ConnectFunction
        // What checkForUpdates does is update the ConnectFunction itself, which corresponds to a listener, depending on the props
        subscription.onStateChange = checkForUpdates
        subscription.trySubscribe()

        // Run once after the first rendering to synchronize data from the store
        checkForUpdates()
        // Returns a function to unsubscribe from when a component is uninstalled
        const unsubscribeWrapper = () => {
          didUnsubscribe = true
          subscription.tryUnsubscribe()
          if (lastThrownError) {
            throw lastThrownError
          }
        }

        return unsubscribeWrapper
      }, [store, subscription, childPropsSelector])

      // Inject the props of the component into the real component we passed in
      const renderedWrappedComponent = useMemo(
        () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
        [forwardedRef, WrappedComponent, actualChildProps]
      )

      const renderedChild = useMemo(() => {
        if (shouldHandleStateChanges) {
          // If this component is subscribed to store updates, we need to pass its own
          // subscription instance down to our descendants. That means rendering the same
          // Context instance, and putting a different value into the context.
          /*
          * Interpretation:
            If this component subscribes to updates from store s, it needs to download instances it subscribes to, meaning it and it
            All descendant components render the same Context instance, but may put different values into the context

            Layer one more Provider and place the overridden context in value.
            What does this mean?That is, there is a connected component and a nested connected component.
            Ensure that the two subscription s obtained from the context are the same, and that they may both add new values to the context.
            I added one, and so did my subcomponents.The final context is the integration of the value s of all components, and subscription s are always the same
          * */
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          )
        }
        // Depending on the received context, the incoming component, and the change in the value of the context, it determines whether or not to re-render
        return renderedWrappedComponent
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])

      return renderedChild
    }

    // Rendering logic based on pure
    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction

    // Add Component Name
    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = displayName

    // If forwardRef is true, ref is injected into the Connect component for easy access to DOM instances of the component
    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect {...props} forwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }
    // Static methods to preserve components
    return hoistStatics(Connect, WrappedComponent)
  }
}

Having looked at the source code, let's summarize the update mechanism for the connect ed component in React-Redux as a whole:
Three of these elements are essential:

  • Depending on who changes (store)
  • Update function (checkForUpdates)
  • Subscription linking store s to update functions

Within the connectAdvanced function, get the store from the context, get the subscription instance (possibly from the context or a new creation), and create the update function checkForUpdates.
Subscribe or resubscribe when a component is initialized, or when a store, Subscription instance, or selector changes.Every time a component is updated, check to see if the store has changed and notify the update if it has changed.
In fact, checkForUpdates is executed, essentially calling the built-in reducer update component.Each update causes the selector to recalculate, so the component always gets the latest props.So, the bottom level of the update mechanism
This is achieved through ConneAdvanced's built-in Reeducer.

summary

So far, the source code for React-Redux has been interpreted around the common functions.Back to the first three questions in the article:

  • How Provider puts store s into context s
  • How to inject a state and dispatch (or a function that calls dispatch) from the store into the props of a component
  • We all know that in Redux, store changes and UI updates can be achieved by subscribing to a function of an update page through store.subscribe(), and how React-Redux does that

store changes, and connect ed components update

Now it should be clear that these three questions correspond to the three core concepts of React-Redux:

  • Provider injects data from top level
  • Selector generates props for components
  • Update mechanism for React-Redux

They work together, which is how React-Redux works: Provider puts data into context, connect s removes store from context, gets mapStateToProps, mapDispatchToProps, and generates Selector as props injection component using selectorFactory.Subscribe to store changes and get the latest props for each update component.

The best way to read the source code is to identify the problem and read it purposefully.At the beginning, I was hard-headed. The more I looked, the more I lost sight of it. I learned a lot after I changed the way I thought you were.

Welcome to my public number: one front end at a time, sharing what I understand on an irregular basis

Posted by Dorin85 on Sat, 31 Aug 2019 06:08:09 -0700