Implement React Redux from Scratch (Part 1)
Open the Black Box of “Connect”
“Technically you could write the container components by hand using
store.subscribe(). We don't advise you to do this because React Redux makes many performance optimizations that are hard to do by hand. For this reason, rather than write container components, we will generate them using the
connect()function provided by React Redux …” — redux.js.org
When I read the redux tutorial, I got confused about the container component in react-redux due to the fact that most of the redux examples use the magic ‘connect’ method to build them as black boxes. I really have no idea how those components got connected to the store and where the ‘state’ live. Are these containers stateful or stateless? What happened when store state changes? Does each of them subscribe to the store independently? And what kind of optimization it does to improve the performance?
I hate the black box as it prevents me from understanding the lifecycle underneath. And finally, I made up the decision to jump into the source code to figure out how react-redux is actually implemented. Reading the source code is both joyful and painful. I was amazed by the optimization tricks the author made, but since it is built as a generic tool for dealing with different scenarios and edge cases, it is difficult for me to sort out the core functionality at the first glance.
That’s why I decide to write this story. I wish to show you how to implement a simplified version of react-redux, which only focuses on the most important features, step by step.
Although It is not the same as source code, similar variable and function names are used to help to understand the original version.
So first, what react-redux does?
- It provides a magic connect function that generates a container component.
- It provides a Provider component that exposes a context for the child components to access the redux store.
To simplify the implementation, let’s assume that the only of input for the connect function is two mapping functions: mapStateToProps and mapDispatchToProps.
What is the “Connect” function
In summary, the connect function is a Higher Order Function that returns a Higher Order Component. The return Higher Order Component will then return the Container Component.
//step1: use connect and mapping functions to make the HOC
HOC = connect(mapStateToProps, mapDispatchToProps)
//step2: use the HOC and wrappedComponent to make the container component
Container = HOC(wrappedComponent)
The return Container Component is aware of the redux store, it will inject props derived from the mapping function into the wrapped presentational component.
Let’s see the basic structure of react-redux’s
Notice inside the connect function, it calls another function(HOF)
connectHOC, to generate the return HOC.
Here we just pass the mapping functions (
mapDispatchToProps) directly to
connectHOC. In the source code, react-redux uses some factory functions to generate a proxy function for each mapping function, make it more configurable and robust. It also accepts additional arguments such as mergeProps and options. However, let’s keep it simple here.
connectHOC is the key of react-redux. It is default as
connectAdvanced in the source code. To get a basic idea of what it is, let’s see the following demo code.
Above shows the basic structure of
connectHOC. As said, it returns the actual HOC, the
wrapWithConnect, which will then return the Container Component
Connect (here you are!). This Container Component is in charge of doing two tasks:
- It subscribes to the store with a callback
onStateChange. When it is called, it calls
setState(with a dummy empty object) to trigger a re-render.
- It calls the
mapDispatchToPropsfunctions to get the merged props to be injected into the wrapped component. Now we hide the logic behind the
magicfunction and I will explain it in part3.
Some redux tutorials use
forceUpdateinstead of the dummy state approach to force re-rendering of the component on store’s state change. The difference is that
forceUpdatewon’t, which gives a chance for further optimization when re-render is unnecessary.
So, how does this
Connect Container subscribe to the store? And how does it parse the data source, such as store state and its own props, and generate the merged props for the wrapped component? In the next two stories I am going to show you how react-redux solves these two major problems for the container component.
Complete Version of Code: here