Abstract: This article focuses on the flow of data under the React-Redux architecture, including distribution and update of data, based on a simple example. This article is based on the hooks pattern, following an introduction to the traditional patternThe react - redux7.2.4

The traditional model

A typical React-Redux application would have the following structure:

import React from 'react'
import ReactDOM from 'react-dom'
import { connect,Provider } from "react-redux"
import { createStore } from "redux"

const initialState = { value: 0 }
const ADD_ACTION = 'add';
const reducer = (state = initialState ,action ) = > {
  switch(action.type){
    case ADD_ACTION: 
      return {
        value: ++state.value
      }
      
    default: 
      break; 
  }
  return state
}
const store = createStore(reducer);

const mapStateToProps = state= > {
    return{
        value: state.value
    }
}
const mapDispatchToProps = dispatch= > {
    return {
        update(payload){
            dispatch({type: ADD_ACTION,payload})
        }
    }
}

const App = connect(mapStateToProps,mapDispatchToProps)( function InnerCommponent({value,update}){
  return(
    <div>Value: {value}<div>
        <button onClick={ update} >Click on the update</button>
      </div>
    </div>)})const rootElement = document.getElementById('root')
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
Copy the code

Based on this code, let’s go through it from top to bottom

Article 1storeWhat the hell is it

The store was created by createStore(Reducer), and the final store has the following data structure:

  {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
Copy the code

Subscribe is used to complete the subscription,dispatch is used to initiate a state update and call back subscribe, getState returns the current state,replaceReducer is used to replaceReducer. The subscription process is as simple as adding a callback function to an array:

. nextListeners.push(listener) ...Copy the code

The listeners are traversing the process, executing the callbacks in turn:

. const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } ...Copy the code

Article 2connectWhat is it, and what does it do

Connect has the following functions:

  1. Initialize themapStateToProps.mapDispatchToProps, as well asmergeProps(this isconnectThe third argument), mainly parameter verification, is then wrapped with a uniform signature:initProxySelector(dispatch, { displayName })The function.
  2. Initialize theselectorFactoryThis function is used to generate props for the final component we defined.
  3. More work, in fact, forconnectAdvancedFunction provides initial input parameter configuration.

The result of the connectAdvanced function is returned.

In addition to basic initialization parameters, connectAdvanced takes the context, which is initialized at Provider build time, and passes store and subscription parameters. A brief introduction to Subscription, which was introduced earlier:

Subscription is an instance of the subscription class that encapsulates the subscription logic of a component to a store. It also processes the subscription logic of nested child components, ensuring that updates are made from parent to child. The trySubscribe method is called to complete the subscription.

Subscription continued, connectAdvanced. ConnectAdvanced eventually returns the wrapWithConnect function, which is the App in the code above.

WrapWithConnect, which can be thought of as a higher-order component, takes WrappedComponent, which is a React component, as an input. WrappedComponent does several things inside:

  • Initialize thecreateChildSelector, which calculates the value of the current componentprops
  • Initialize theConnectFunction, the main functions of the interior are:
    • Initialize the Subscription using useMemo. When the Subscription instance is initialized, the child component’s Subscription is normally passed into the parent component’s Subscription. Parent component Subscription is passed by the context taken by connectAdvanced.

    • With useLayoutEffect(client) or useEffect(SSR), complete the subscription (source code made a simple modification):

      . subscription.onStateChange = checkForUpdates subscription.trySubscribe() ... .Internal trySubscribe / / subscription
        // When handleChangeWrapper is finally executed, onStateChange is invoked
        this.parentSub.addNestedSub(this.handleChangeWrapper)
        ...
        // addNestedSub.this.listeners.subscribe(listener)
        ...
        
      
      Copy the code

      As you can see, subscriptions within the child component end up hanging on the subscription listeners passed down from the parent component. This completes the subscription.

      Subscription listeners are internal data structures that can be used to complete their subscriptions, using bidirectional lists:

      {
          callback,
          next: null,
          prev: null,
      }
      Copy the code

      Subscribe (listener) :

      let listener = (last = { callback, next: null, prev: }) if (listener.prev) {listener.prev. Next = listener} else {listener.prev. Next = listener}Copy the code

      When invoked, the code looks like this:

      . batch(() => { let listener = first while (listener) { listener.callback() listener = listener.next } }) ...Copy the code

      The batch function is called batchedUpdates$1 after the React function is executed. This function will change — an optional executionContext value (– an optional executionContext | = BatchedContext), the direct result is when the React to update, Instead of synchronous updates like renderRootSync, scheduleUpdateOnFiber executes an asynchronous callback that merges all updates. This step can be called performance tuning.

    • After calculating the actualChildProps, the final return is:

      <ContextToUse.Provider value={overriddenContextValue}>
       {renderedWrappedComponent}
      </ContextToUse.Provider>
      Copy the code

      The overriddenContextValue data structure is the same as the value of the context, but the subscription is replaced by the current component’s subscription. The reason for wrapping providers around components is that React always calls the context nearby. This ensures that nested children of renderedWrappedComponent, if accessing ContextToUse, always get the same context instance as the current component. And when a child component has a subscription behavior, it can subscribe to its own addNestedSub, to ensure that the update from parent to child.

  • The final will beWrappedComponentStatic properties of theConnectFunctionOn the backConnectFunction.

Article 3Provider

In fact, it is a common component that does several things:

  • To initialize itselfsubscription.
    const subscription = new Subscription(store)
    Copy the code

    And collaborativestoreuseuseMemoHang togethercontextValueon

  • withuseLayoutEffect(Client) oruseEffect(SSR), complete the subscription (source code made a simple modification):
    . subscription.onStateChange = subscription.notifyNestedSubs ... subscription.trySubscribe() ...Internal trySubscribe / / subscription.this.store.subscribe(this.handleChangeWrapper)
    ...
    // Store. Subscribe nextListeners are listeners to an array of listeners
    nextListeners.push(listener)
    ...
    Copy the code
  • The last return
    <Context.Provider value={contextValue}>{children}</Context.Provider>
    Copy the code

ConnectFunction and Provider are called during the beiginWor phase of the React update process.

After introducing the basic concepts, we can start to introduce the update and distribution of data.

Chapter 4 Data Distribution

Distributing data is relatively simple. For example, when the data is updated, there is only one external window of the original component after operation with CONNECT, that is, props. Therefore, the updated data is mainly the calculation of props.

ConnectFunction internally evaluates props from childPropsSelector(store.getState(), wrapperProps), through a long link. We don’t need to focus on the details, but we can give the final result:

// pureFinalPropsSelectorFactory. stateProps = mapStateToProps(state, ownProps) dispatchProps = mapDispatchToProps(dispatch, ownProps) mergedProps = mergeProps(stateProps, dispatchProps, ownProps) ...Copy the code

The result is a combination of the stateProps, dispatchProps, and ownProps. Based on this, the component can read data from the Functions distributed by react-Redux.

Chapter 5 Data Update

An update is initiated by Dispatch, which basically updates the state and invokes the subscription callback function

An overview of the process

Hooks mode

Article 1useSelectorData distribution

Start with function signatures

useSelector(selector, equalityFn = refEquality)
Copy the code

So selector is a custom function, when you call useSelector, it passes in the current state as an argument, and the thing that eventually returns is what we call selectedState equalityFn and that’s what determines whether or not the value that the selector returns has changed

There is also an internal implementation of the subscription function, which is implemented in the same way as the subscription function. The subscription function is used to update selectedState:

/ / checkForUpdates inside.const newStoreState = store.getState()
const newSelectedState = latestSelector.current(newStoreState)

if (equalityFn(newSelectedState, latestSelectedState.current)) {
  return
}

latestSelectedState.current = newSelectedState
latestStoreState.current = newStoreState
// If an update is actually performed, this function is still useReducer dispatch
forceRender()
...
Copy the code

Every time useSelector executes, it takes the latest selectedState from the store and returns it. At the same time, only one of the selector and storeState changes, or the subscription function changes when it executes, and equalityFn’s execution result is false, which means that the selectedState before and after does change, The value of selectedState is updated.

Article 2useDispatchImplement data update

The emMM is called store.dispatch