抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
825 words
4 minutes
React-Redux Source Code: A Quick Read

Overview#

First, let’s look at an image:

React-Redux

As you can see, the entry file imports these modules:

  • Provider
  • connectAdvanced
  • connect

Let’s examine these modules one by one.

Provider#

It mounts the store onto the context, giving child components the ability to access the store.

connectAdvanced#

A higher-order function used to generate connect, which references the following modules:

import { Component, createElement } from "react";
import hoistStatics from "hoist-non-react-statics";
import invariant from "invariant";

import { storeShape, subscriptionShape } from "../utils/PropTypes";
import Subscription from "../utils/Subscription";

Subscription#

First, let’s look at the Subscription module.

createListenerCollection#

Implements a publish-subscribe pattern, providing clear, notify, and subscribe methods. These are used to clear the current queue, trigger events, and subscribe to methods, respectively.

Subscription#

A subscription class that subscribes to the onStateChange event. It also ensures that if a parent also has an event subscription, the parent’s event is triggered first, followed by the current component’s event, when an event occurs.

makeSelectorStateful(sourceSelector,store)#

function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props);
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true;
          selector.props = nextProps;
          selector.error = null;
        }
      } catch (error) {
        selector.shouldComponentUpdate = true;
        selector.error = error;
      }
    },
  };

  return selector;
}

The selector is used to compute the props for the next state based on state and prop, and then pass them to the connected component. This function is a wrapper around the selector that, in addition to computing props, also records the result of the previous computation.

connectAdvanced(selectorFactory,{options})(WrappedComponent)#

The primary function of connectAdvanced is to return a higher-order component that generates a Connect component, based on the provided selectorFactory and options.

connect#

    class Connect extends Component {
      constructor(props, context) {
        super(props, context)
        ...
        this.initSelector()
        this.initSubscription()
      }
      ...
      initSelector() {
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        this.selector.run(this.props)
      }

      initSubscription() {
        if (!shouldHandleStateChanges) return

        // parentSub's source should match where store came from: props vs. context. A component
        // connected to the store via props shouldn't use subscription from context, or vice versa.
        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))

        // `notifyNestedSubs` is duplicated to handle the case where the component is  unmounted in
        // the middle of the notification loop, where `this.subscription` will then be null. An
        // extra null check every change can be avoided by copying the method onto `this` and then
        // replacing it with a no-op on unmount. This can probably be avoided if Subscription's
        // listeners logic is changed to not call listeners that have been unsubscribed in the
        // middle of the notification loop.
        this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
      }
      ...
      render() {
        const selector = this.selector
        selector.shouldComponentUpdate = false

        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
      }
    }

This component, defined within connectAdvanced, retrieves data from the store via context, initializes a selector containing the initial store using the initSelector method, and subscribes to store changes via the initSubscription method. The render method of this component merely acts as a proxy, passing the props computed by the selector to the wrapped component.

connect#

connect imports the following modules:

import connectAdvanced from "../components/connectAdvanced";
import shallowEqual from "../utils/shallowEqual";
import defaultMapDispatchToPropsFactories from "./mapDispatchToProps";
import defaultMapStateToPropsFactories from "./mapStateToProps";
import defaultMergePropsFactories from "./mergeProps";
import defaultSelectorFactory from "./selectorFactory";

It simply builds upon connectAdvanced by passing selectorFactory, mapStateToPropsFactories, mapDispatchToProps, and mapDispatchToPropsFactories as options parameters to connectAdvanced.

defaultSelectorFactory#

Defines the behavior of the default selector, which computes the props for the next state from the incoming state.

defaultMap xxx To xxx Factories#

These modules are used to generate factory functions that map to props methods. Their code takes the following form:

import { bindActionCreators } from "redux";

import { wrapMapToPropsConstant, wrapMapToPropsFunc } from "./wrapMapToProps";

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return typeof mapDispatchToProps === "function"
    ? wrapMapToPropsFunc(mapDispatchToProps, "mapDispatchToProps")
    : undefined;
}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return !mapDispatchToProps
    ? wrapMapToPropsConstant((dispatch) => ({ dispatch }))
    : undefined;
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return mapDispatchToProps && typeof mapDispatchToProps === "object"
    ? wrapMapToPropsConstant((dispatch) =>
        bindActionCreators(mapDispatchToProps, dispatch),
      )
    : undefined;
}

As you can see, they primarily reference the wrapMapToProps module.

wrapMapToProps#

wrapMapToPropsFunc#

This method is a proxy function that wraps the mapToProps function, performing the following tasks:

  • Detects whether the mapToProps function’s invocation depends on props, which helps selectorFactory decide whether to re-invoke it when props change.
  • During the first invocation, if mapToProps returns another function after execution, it processes that function and uses it as the new mapToProps for subsequent calls.
  • During the first invocation, it validates whether the result of the call is a plain object. This is to warn developers if their mapToProps function does not return a valid result.

Summary#

React-Redux is a library used to bind Redux to React. While it’s possible to use Redux directly within React without React-Redux, it’s overly cumbersome. It would require manually listening for store changes and manually triggering component render methods. React-Redux provides a higher-order component, connect, to handle this for us, and it also includes several optimizations. All we need to do is provide mapDispatchToProps and mapStateToProps to the components that need to listen to the store.

This article was published on August 4, 2017 and last updated on August 4, 2017, 2985 days ago. The content may be outdated.

React-Redux Source Code: A Quick Read
https://blog.kisnows.com/en-US/2017/08/04/react-redux-source-code-read/
Author
Kisnows
Published at
2017-08-04
License
CC BY-NC-ND 4.0