Implement React Redux from Scratch (Part 2)
Subscription and Notification
Previous Story: Implement React Redux from Scratch (Part 1)
Subscribing container to the store is one of the most important functional requirement in react-redux. To limit the scope of the problem here, let’s focus on the most general scenario: Use
Provider to expose the
store in the context for all the child components.
The order matters
It seems intuitive to subscribe to the
componentDidMount for each component since that’s when the UI gets settled down. But hold on! Do we subscribe each component to the single store passed down from the context? If so, since descendant components get mounted before ancestors, store notification will then trigger re-render of descendants before ancestors, which conflicts with the normal top-down data flow.
Think about the “source of truth” for the child component. It might come from the redux store as well as its parent (as props). The data source flows top down and thus notification and update should follow this pattern to avoid unnecessary re-rendering.
To cater the “top-down” or “unidirectional” data flow of React, the order of notification is important. And react-redux uses a very smart trick to ensure the ancestor components get notified and re-render before descendants.
The key to the trick is that each container component maintains its own subscription and notification system, thus instead of relying on the single store to notify every container, each container will be in charge of notifying the child components “below” it. You will see in a moment that react-redux has a special utility class for constructing this “system” called
You can refer Dan Abramov’s tutorial video on egghead.io to see how he implements the store and its subscription system from scratch. It is a typical observation pattern using an array to store the listeners and notify them in order when the state changes.
But let’s take a step back and start from the
Provider, which “provides” the context for components.
Surprise! Besides the
Provider also provides another context variable called
parentSub refers to the
Subscription instance of the ancestor container and it is initialized as
null. However, this context variable gets updated when it is passed down! (check React’s document on Context to see how it get’s updated)
The above updated
Connect Component does two additional things:
- In the constructor, it uses its
parentSubto configure its own subscription.
- In the getChildContext, it replaces the parent’s subscription with its own subscription so its child component can get access to it.
Notice that when its
onStateChange (the state change listener) gets called, it first resets itself, after that (
componentDidUpdate), it uses its own
subscription to notify the nested subscription (by calling its
notifyNestedSubs function). That’s how it maintains the order of notification. The ancestor’s
onStateChange get called first, update itself, then notify the descendants’
The question is how it maintains the order of subscription? How it ensures that the root container is notified at the very beginning?
The solution lies in the
trySubscribe call when the container is mounted. It’s time to introduce the
You can see that a subscription has the
notifyNestedSubs method defined in the
subscriptionShape. It also has another method
addNestedSub which will be called in the
trySubscribe. To illustrate the subscribing process, let’s image the scenario below:
Container2 will be mounted first and trigger the
trySubscribe method of its own subscription instance. What this method does is to try to subscribe the callback (
onStateChange) function to its parent’s (Container1) subscription system by calling its
addNestedSubs, it(the subscription of Container1) will first call its own
trySubscribe method and repeat the process above. However, since the root container does not have any ancestor Container, its
parentSub will be the initial
null value from the
Provider. In this case, it will subscribe its’ own
onStateChange to the
This recursive process ensures that only the
onStateChange callback of the root container subscribes to the
store and the following
onStateChange will subscribe to the
parentSub of each component.
You can test with the complete code to see how it works. In summary, the bottom-up subscribing process when mounted and the top-down notification process when state change help to ensure the order of re-rendering of the Redux app.
In the next story, I will show you another important part of react-redux: Selector, which is how we parse the raw data such as store state and own props and get the merged props to be injected to the wrapped component. It is where most of the optimizations take place.
Next Story: Implement React Redux from Scratch (Part 3)