From 0febbe14dea2eff07689a13a60f757f467b751f4 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 11 Feb 2015 11:15:31 -0800 Subject: [PATCH] Tweaks on advanced performance --- docs/advanced-performance.html | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/advanced-performance.html b/docs/advanced-performance.html index 308f50a955..d32d52b572 100644 --- a/docs/advanced-performance.html +++ b/docs/advanced-performance.html @@ -390,35 +390,35 @@

One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React version. The idea of re-rendering an entire subtree of components in response to every state change makes people wonder whether this process negatively impacts performance. React uses several clever techniques to minimize the number of costly DOM operations required to update the UI.

Avoiding reconciling the DOM #

-

React makes use of a virtual DOM, which is a descriptor of a DOM subtree rendered in the browser. This parallel representation allows React to avoid creating DOM nodes and accessing existing ones, which is slower than operations on JavaScript objects. When a component's props or state change, React decides whether an actual DOM update is necessary by constructing a new virtual DOM and comparing it to the old one. Only in the case they are not equal, will React reconcile the DOM, applying as few mutations as possible.

+

React makes use of a virtual DOM, which is a descriptor of a DOM subtree rendered in the browser. This parallel representation allows React to avoid creating DOM nodes and accessing existing ones, which is slower than operations on JavaScript objects. When a component's props or state change, React decides whether an actual DOM update is necessary by constructing a new virtual DOM and comparing it to the old one. Only in the case they are not equal, will React reconcile the DOM, applying as few mutations as possible.

-

On top of this, React provides a component lifecycle function, shouldComponentUpdate, which is triggered before the re-rendering process starts, giving the developer the ability to short circuit this process. The default implementation of this function returns true, leaving React to perform the update:

+

On top of this, React provides a component lifecycle function, shouldComponentUpdate, which is triggered before the re-rendering process starts, giving the developer the ability to short circuit this process. The default implementation of this function returns true, leaving React to perform the update:

shouldComponentUpdate: function(nextProps, nextState) {
   return true;
 }
 

Keep in mind that React will invoke this function pretty often, so the implementation has to be fast.

-

Say you have a messaging application with several chat threads. Suppose only one of the threads has changed. If we implement shouldComponentUpdate on the ChatThread component, React can skip the rendering step for the other threads:

+

Say you have a messaging application with several chat threads. Suppose only one of the threads has changed. If we implement shouldComponentUpdate on the ChatThread component, React can skip the rendering step for the other threads:

shouldComponentUpdate: function(nextProps, nextState) {
   // TODO: return whether or not current chat thread is
   // different to former one.
 }
 
-

So, in summary, React avoids carrying out expensive DOM operations required to reconcile subtrees of the DOM by allowing the user to short circuit the process using shouldComponentUpdate, and, for those which should update, by comparing virtual DOMs.

+

So, in summary, React avoids carrying out expensive DOM operations required to reconcile subtrees of the DOM by allowing the user to short circuit the process using shouldComponentUpdate, and, for those which should update, by comparing virtual DOMs.

shouldComponentUpdate in action #

-

Here's a subtree of components. For each one is indicated what shouldComponentUpdate returned and whether or not the virtual DOMs were equivalent. Finally, the circle's color indicates whether the component had to be reconciled or not.

+

Here's a subtree of components. For each one is indicated what shouldComponentUpdate returned and whether or not the virtual DOMs were equivalent. Finally, the circle's color indicates whether the component had to be reconciled or not.

-

In the example above, since shouldComponentUpdate returned false for the subtree rooted at C2, React had no need to generate the new virtual DOM, and therefore, it neither needed to reconcile the DOM. Note that React didn't even had to invoke shouldComponentUpdate on C4 and C5.

+

In the example above, since shouldComponentUpdate returned false for the subtree rooted at C2, React had no need to generate the new virtual DOM, and therefore, it neither needed to reconcile the DOM. Note that React didn't even had to invoke shouldComponentUpdate on C4 and C5.

-

For C1 and C3 shouldComponentUpdate returned true, so React had to go down to the leaves and check them. For C6 it returned true; since the virtual DOMs weren't equivalent it had to reconcile the DOM. +

For C1 and C3 shouldComponentUpdate returned true, so React had to go down to the leaves and check them. For C6 it returned true; since the virtual DOMs weren't equivalent it had to reconcile the DOM. The last interesting case is C8. For this node React had to compute the virtual DOM, but since it was equal to the old one, it didn't have to reconcile it's DOM.

-

Note that React only had to do DOM mutations for C6, which was inevitable. For C8 it bailed out by comparing the virtual DOMs, and for C2's subtree and C7, it didn't even have to compute the virtual DOM as we bailed out on shouldComponentUpdate.

+

Note that React only had to do DOM mutations for C6, which was inevitable. For C8 it bailed out by comparing the virtual DOMs, and for C2's subtree and C7, it didn't even have to compute the virtual DOM as we bailed out on shouldComponentUpdate.

-

So, how should we implement shouldComponentUpdate? Say that you have a component that just renders a string value:

+

So, how should we implement shouldComponentUpdate? Say that you have a component that just renders a string value:

React.createClass({
   propsTypes: {
     value: React.PropTypes.string.isRequired
@@ -429,14 +429,14 @@ The last interesting case is C8. For this node React had to compute the virtual
   }
 });
 
-

We could easily implement shouldComponentUpdate as follow:

+

We could easily implement shouldComponentUpdate as follow:

shouldComponentUpdate: function(nextProps, nextState) {
   return this.props.value !== nextProps.value;
 }
 

So far so good, dealing with such simple props/state structures is easy. We could even generalize an implementation based on shallow equality and mix it into components. In fact, React already provides such implementation: PureRenderMixin.

-

But what if your components' props or state are mutable data structures?. Say the prop the component receives, instead of being a string like 'bar', is a Javascript object that contains a string such as, { foo: 'bar' }:

+

But what if your components' props or state are mutable data structures?. Say the prop the component receives, instead of being a string like 'bar', is a Javascript object that contains a string such as, { foo: 'bar' }:

React.createClass({
   propsTypes: {
     value: React.PropTypes.object.isRequired
@@ -447,13 +447,13 @@ The last interesting case is C8. For this node React had to compute the virtual
   }
 });
 
-

The implementation of shouldComponentUpdate we had before wouldn't always work as expected :

+

The implementation of shouldComponentUpdate we had before wouldn't always work as expected:

// assume this.props.value is { foo: 'bar' }
 // assume nextProps.value is { foo: 'bar' },
 // but this reference is different to this.props.value
 this.props.value !== nextProps.value; // true
 
-

The problem is shouldComponentUpdate will return true when the prop actually didn't change. To fix this we could come up with this alternative implementation:

+

The problem is shouldComponentUpdate will return true when the prop actually didn't change. To fix this we could come up with this alternative implementation:

shouldComponentUpdate: function(nextProps, nextState) {
   return this.props.value.foo !== nextProps.value.foo;
 }
@@ -482,11 +482,11 @@ The last interesting case is C8. For this node React had to compute the virtual
 

The first time the inner component gets rendered it will have { foo: 'bar' } as the value prop. If the user clicks on the anchor, the parent component's state will get updated to { value: { foo: 'barbar' } }, triggering the re-rendering process of the inner component, which will receive { foo: 'barbar' } as the new value for the prop.

-

The problem is that since the parent and inner components share a reference to the same object, when the object gets mutated on line 2 of the onClick function, the prop the inner component had will change. So, when the re-rendering process starts, and shouldComponentUpdate gets invoked, this.props.value.foo will be equal to nextProps.value.foo, because in fact, this.props.value references the same object as nextProps.value.

+

The problem is that since the parent and inner components share a reference to the same object, when the object gets mutated on line 2 of the onClick function, the prop the inner component had will change. So, when the re-rendering process starts, and shouldComponentUpdate gets invoked, this.props.value.foo will be equal to nextProps.value.foo, because in fact, this.props.value references the same object as nextProps.value.

Consequently, since we'll miss the change on the prop and short circuit the re-rendering process, the UI won't get updated from 'bar' to 'barbar'.

Immutable-js to the rescue #

-

Immutable-js is a Javascript collections library written by Lee Byron, which Facebook recently open-sourced. It provides immutable* persistent* collections via structural sharing. Lets see what these properties mean:

+

Immutable-js is a Javascript collections library written by Lee Byron, which Facebook recently open-sourced. It provides immutable persistent collections via structural sharing. Lets see what these properties mean: