Overview
First, let’s look at an image:
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 onprops
, which helpsselectorFactory
decide whether to re-invoke it whenprops
change. - During the first invocation, if
mapToProps
returns another function after execution, it processes that function and uses it as the newmapToProps
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.