[source code interpretation] react Redux

Time:2021-8-31

Interpretation of react Redux source code

[TOC]

Pre knowledge

Before reading this article, please make sure you understand the following knowledge:

  • react
  • redux
  • High order component
  • React diff mechanism

It doesn’t matter if you don’t know much about high-order components. You just need to know that high-order components are factory functions that receive a component class (or function component) and return a modified new component class.connectIs a high-order component.

Abbreviations that will be used in the article

  • hoc: high order component
  • scu: shouldComponentUpdate

Issues

As we know, react Redux provides developers with Redux to react binding. This paper does not make a detailed analysis of the source code line by line. It is better to make a general overview of the source code based on the following problems.

We focus on:

  • How does connect connect the store?
  • How to distribute state trees to sub components?
    The provider only maintains a store instance. Even if the state tree in the store changes, the react diff stage only makes shallow comparison and only compares object references. Therefore, the provider.props.store is regarded as unchanged, so the new state tree cannot be distributed to sub components.

We know that connect also receives several configuration functions formapXxToPropsAnd whether the setting is performedpureOptimization, etc. These are not interesting, so we don’t care.

Two core APIs

React Redux has only two core parts

  • Provider
  • connect

Provider

ProviderThe role of is only to maintain onestoreInstance and usecontext, provides a mechanism for distributing state trees

connect

connectAs the name suggests, connect the container and store so that the container can respond to changes in the state tree.

connectItself is a high-level component, which callsconnectAdvancedSuch a factory function.

The two problems mentioned above are all caused byconnectAdvancedEncapsulated.

connectAdvanced

connectAdvancedIs a factory function of a high-order component, which returns a hoc (high-order component), which finally returns aConnect(WrappedComponent)Components of.

How does connect connect the store?

In thisConnect(WrappedComponent)In, it is stated thatgetChildContextFunction to get the shared store instance in the provider.

NOTE: theoretically, we can declare any sub component of the provider as long as we also declare itgetChildContextSo it’s likeconnectAfter that, you can get the store instance.

How does connect distribute state trees to subcomponents?

In a word, mainly throughstore.subscribeAutomatic re render mechanism for interface and component props changes.

In react Redux, we define several things to help us do the above things.

  • selector
  • subscriptions

eachConnect(WrappedComponent)Init is required in both constructors.

selector

selectorMainly byselectorFactoryStructure,selectorInternal inclusion

  • A generating function for calculating new props
    sourceSelector(store.getState(), props)
    amongpropsIt is cached by the selector before, and will be directly used in the futurestore.getState()Compare references
  • ID of a shouldcomponentupdate
    stayConnect(WrappedComponent)Internally, it is directly used to judge SCU
shouldComponentUpdate() {
  return this.selector.shouldComponentUpdate
}    

The source code is as follows

const selector = {
    //The logic of the actual run is defined in the selectorfactory, which is just SCU
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        //The actual SCU logic of connect is defined here
        //Compare whether the references of props remain equal after run
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

callselector.runAt the time of, I mainly did two things

  • Generate new props based on the prevprops cached previously and the state tree in the current Redux store instance.
  • Update the shouldcomponentupdate judgment ID by comparing references

Connect(WrappedComponent)Will be called in three placesselector.run

  • componentDidMount
  • componentWillReceiveProps
  • onStateChanged

Then react will automatically distribute the changed props to the sub components to realize re render.

subcription

We mentioned above that the provider itself cannot notify the state tree of changes. Therefore, in order to monitor the state tree changes, we need to register some listeners with the provider’s store instance through the store.subcribe interface.

As we already know, in Redux, store.subcribe allows users to register listeners, which will be triggered after each dispatch execution.

So in react Redux,Connect(WrappedComponent)The subscript in is finally registered in the store.

initSubscription() {
    //... omit some irrelevant code
    this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
}

Among them,

  • Parentsub is used to execute subscription in nested connect.
  • new SubscriptionIt mainly constructs a containingtrySubscribeObject of method
trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)

      this.listeners = createListenerCollection()
    }
  }

Connect(WrappedComponent)staycomponentDidMountTimetrySubscribe, incomponentWillUnmountTimetryUnsubscribe

The main logic of trysubscribe is toonStateChangeRegister in redux.store.

As we have said, onstatechange is mainly to executeselector.run

onStateChange() {
  //Get new props and state
  this.selector.run(this.props)

  if (!this.selector.shouldComponentUpdate) {
    this.notifyNestedSubs()
  } else {
    this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
    this.setState(dummyState)
  }
}

Therefore, every time the state tree changes, or more accurately, every time the dispatch succeeds, redux.store will execute through the registered subscriptionConnect(WrappedComponent)MediumonStateChange, and then byselector.runTo determine whether a new props needs to be generated.