Fast web experience is what users expect nowadays. Fast mobile web experience is essential when most people are accessing websites with their mobile devices. And it’s only becoming more critical now that google takes website loading speed as a ranking factor.
Improving/maximizing React/Redux application performance therefore should be treated as a top priority.
Minimize unnecessary re-render
This could possibly the most important performance tuning you can do. The React framework is very flexible that you can code your application in many different ways. However, some ways are better than the others. Coding it correctly can make a huge difference.
Use PureComponent whenever possible
A react component will re-render when there’re changes to props or state. Also, the react component is re-rendered when it’s parent component is re-rendered even though props/state of the component has not changed. To avoid this unnecessary re-render, the component should extend from React.PureComponent. A PureComponent implements shouldComponentUpdate() to perform a shallow prop and state comparison and no re-render is performed when props/state stay the same.
Shallow comparison is used because it is fast and efficient. However, there’s a few things we need to be careful or otherwise, unexpected behavior will arise. When performing shallow comparison, only simple data types are compared by values. Complex types (object, array and function), however, are compared by reference, not the actual data or value stored. Therefore, we should use the same reference when there is no change and should create a new instance when there are changes.
object
1 2 3 |
render() { return <MyComponent data={{ value: 4 }} />; } |
1 2 3 4 5 |
// do this instead data = { value: 4 }; render() { return <MyComponent data={this.data} />; } |
array
1 2 3 |
render() { return <MyComponent data={[1, 2, 3]} />; } |
1 2 3 4 5 |
// do this instead data = [1, 2, 3]; render() { return <MyComponent data={this.data} />; } |
function
1 2 3 4 |
clicked() { } render() { return <MyComponent onClick={this.clicked.bind(this)} />; } |
bind(this) is no longer needed for newer javascript syntax/feature. The simplest way is to declare arrow function as variable. Accessing this in arrow function will automatically reference to the component class instance.
1 2 3 4 |
clicked() { } render() { return <MyComponent onClick={() => this.clicked()} />; } |
It’s very convenient to define an inline function. However, this creates a new function everytime.
1 2 3 4 5 |
// do this instead clicked: () => {} render() { return <MyComponent onClick={this.clicked} />; } |
Use Redux efficiently
Simplify data if possible
Let’s look at the following example:
1 2 3 4 5 6 7 8 9 |
class Equal1_Comp extend React.Component<any, any> { render() { var isValueEqual1 = this.props.value === 1; return <div>Value equals to 1: {isValueEqual1 ? 'Yes' : 'No'</div>; } } const Equal1 = connect( state => ({ value: state.value }) )(Equal1_Comp); |
The component displays ‘Yes’ when value is 1 or otherwise ‘No’. Everytime the value changes, the component re-renders. However, the component only cares when value is 1. So we can simplify the data as follow:
1 2 3 4 5 6 7 8 |
class Equal1_Comp extend React.Component<any, any> { render() { return <div>Value equals to 1: {this.props.isValueEqual1 ? 'Yes' : 'No'</div>; } } const Equal1 = connect( state => ({ isValueEqual1: state.value === 1 }) )(Equal1_Comp); |
A boolean prop isValueEqual1 is passed down instead. The component now only re-renders when prop changes between true and false.
Only provide data needed for the component
Avoid providing data that is not consumed by the component. When this extra data is updated, the component will be re-rendered even when the component doesn’t need it.
If a component receive a piece of redux data only to pass down to it’s child component, consider making use of connect() at the child component to receive the data directly. With this modification, the component will not be re-rendered when this piece of data changes, which is only of interests to the child component.
Redux reducer
Whenever a reducer consumes a redux action that would update redux store, ensure new instance is created to form a new state. For reducers that do not consume the redux action, always return the same state reference (no change). This is essential for correct shallow comparison.
Redux connect()
Higher-order component connect() will perform shallow comparison like PureComponent to avoid unnecessary re-rendering. It is OK if you simply return some redux store data without alteration. However, if some computation or calculation is performed to return a complex type property, you would need to use selectors created using reselect‘s createSelector() to ensure same reference (by caching) is returned when the computation result is the same.
Additional benefits for using selectors is that a selector is not recomputed unless one of its arguments changes.
Again, minimizing re-rendering allows react components to run optimally. In part 2, we will look at other aspect of React/redux application to further improve performance.