Implement React Redux from Scratch (Part 3)

Selector and Optimization

previous chapter: Implement React Redux from Scratch (Part 2)

What is the selector? To understand it we have to go back to the ‘responsibilities’ of a Connect container component. Besides subscribing to the store, another job of the container component is to inject some props into the wrapped component (e.g. the presentational component). Those props to be injected is generated in three different ways:

  1. ownProps, its own props received as JSX attributes. (e.g. <Container prop1={xxx} prop2={xxx}/> )
  2. stateProps, which is derived from the mapStateToProps mapping function with the store.state and perhaps its own props.
  3. dispatchProps, which is derived from the mapDispatchToProps mapping function with store.dispatch and perhaps its own props.

A selector is a function that “select” those props from the source data, which includes the store.state, the store.dispatch, and its own props.

function simpleSelector(nextState, nextOwnProps) {
var stateProps = mapStateToProps(nextState, nextOwnProps)
var dispatchProps = mapDispatchToProps(dispatch, nextOwnProps)
var mergedProps = {
...stateProps,
...dispatchProps,
...nextOwnProps
}
return mergedProps
}

The above simpleSelector does the job with the following Connect component, but the problem is that every time the container receives new props from its parent, or when its onStateChange is triggered, it will call the selector and thus run the mapping functions to re-calculate the stateProps, dispatchProps and the return mergedProps. And since every run will return a new object (with the object spread syntax), there is no simple way to determine whether a re-render is needed.

Those mapping functions could take a long time to run, although library like Reselect can help us memorize the mapping functions (check my story on how to implement Reselect), there is still place here locally for optimization.

Optimization 1, make selector memorizable

The first trick react-redux does is to make the selector function itself memorizable so as to reduce the call of the mapping functions. It uses a factory function to provide a scope for caching the last input and output. A simplified version is shown in this gist.

There is no magic. It is similar to what Reselect does to those mapping functions: whenever it is called, its input is compared to the last input to decide whether it needs to call the actual mapping functions. If they are the same (by strictEqual and shallowEqual) then it just returns the previous output, which is the previous mergedProps.

Optimization 2, make selector “stateful”

So far we only talk about optimization at the data level. What about the React component rendering? Immediately we think about the lifecycle method shouldComponentUpdate.

Normally we will do shallow compare on nextProps and nextState (the local React state, here we don’t have it, …well, just a dummy empty object) in shouldComponentUpdate to determine if we need to re-render the wrapped component. However, since the only source of truth for the wrapped component is the mergedProps calculated by the selector, and the selector has the full control over this mergedProps, we can assign the shouldComponentUpdate checking to the selector itself!

Let’s see the demo code.

At the top, we have a function makeSelectorStateful. As its name indicates, it is stateful because it wraps the original selector function and tracks its result between runs. It has a flag shouldComponentUpdate which will be used in the actual react lifecycle method with the same name to determine if re-render is necessary.

There is one additional trick react-redux does to further improve the performance. Originally we have a permanent componentDidUpdate method to notify the nested subscriptions when the current component gets updated. However, if shouldComponentUpdate return false, this componentDidUpdate will never get called, breaking the notification process.

Thus in onStateChange, it has an if-else check on whether the current component needs to re-render. If not, it will go ahead to notify the nested subs. If yes, a temporal componentDidUpdate method is assigned to the component. The temporal method will unimplemented itself when called and notify the nested subs. In this way, it ensures that it will only do the following notification when the update is trigged by onStateChange.

That’s it! Again you can check the complete code for the implementation details. You can import it to play around with it. And don’t forget to check the source code. As mentioned, this is a simplified version of react-redux for demo purpose. Some important features such as unsubscribe, actionCreator is ignored here to make the story short.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.