Pushing React Further: Reacting Fast
When my team and I started developing a new version of the platform, we did several Proofs Of Concept to evaluate the performance and user experience of the applications we wanted the platform to deliver. We also evaluated several JS frameworks popular at the time (Angular, Ember, Knockout, React…) to see which one would get the job done better. In the end, despite Angular’s popularity, we chose React, since we got very promising results in our POCs.
React does quite a nice job when it comes to performance. It prevents (but doesn’t completely stop) web developers from making some of the mistakes that may cause slowness — such as layout thrashing.
By using a declarative render model and its Virtual DOM, React batches and minimizes all the mutations made to the browser’s DOM. This reduces the number of layout computations.
It looked promising, but as the applications grew in complexity we started facing some challenges.
Key for Performance
During our first tests, lists did not have the desired performance, especially concerning updates.
Using keys we were able to speed things up. Keys are developer-defined identifiers given to components which React uses when performing reconciliation between its virtual DOM and the browser’s DOM . Moreover, when you have a list of components, in the Development version, React will complain if keys are missing from the child components of a list.
Fortunately for developers, the OutSystems platform will take care of this and will automatically insert the appropriate keys to optimize list updates
TIP: It’s important that you give meaningful keys to your components, to help React keep track of each element after insertions, moves, changes or removes. Keys should be related to the content of list items they represent and preserve their identity (for example, when feeding a list with items from a database, you can use the record ID as a key).
Should Component Update?
Even with keys, when you have more complex lists (with lots of DOM elements), performance will degrade.
To avoid wasting time rendering components which return the same DOM, React provides a way to skip the rendering of unchanged components. Before a component is rendered, meaning, before its rendering method is called, React provides a hook — shouldComponentUpdate — to check whether a component should be rendered or not, depending on its new state and properties. Skipping the rendering of a component will also skip the rendering of its descendants resulting in a performance boost.
We gave it a try and introduced shouldComponentUpdate for lists and list items, and saw a remarkable increase in performance.
So with this new version, a developer won’t have to worry about this problem, since the platform will take care of it.
TIP: By default, React renders all components, so if you want a performance boost, you have to implement shouldComponentUpdate or use PureRenderMixin.
The shouldComponentUpdate function should be as fast as possible. Comparisons between old and new state/properties done at this point should be made by equals (===) operator. Avoid mutating the state or properties of your components, as it slows down simple (shallow) comparisons.
Using Immutable data structures is great to speed up comparisons because you only have to compare object references — which is cheap. Immutability allows referential comparisons but itis also faster when comparing nested information (common case in OutSystems applications). When the structures graph changes, every node up to the root is also updated (recreated). So there’s no need to perform deep comparisons because every node in the graph is updated whenever any of the descendants change.
Don’t Waste Time: Track Performance with React Tools
Performance is one of our main concerns and we use Chrome and React performance tools daily to make sure everything works smoothly.
React performance tools help us identify which components take longer to render and to find good candidates for a shouldComponentUpdate implementation.
TIP: If you have many instances of the same component and you need to somehow distinguish them in the results of the Perf tools, you can add a displayName (static) field to your component and improve results readability.
Performance of Composable UIs
Having a highly composable UI model like ours introduces some optimization challenges. Even after introducing all these optimizations, we were still behind the native-like performance we aimed at.
Check an example of what we hit below:
<Child somePropA somePropB />
Here we have a Component (WidgetWithExpensiveRender) that is able to receive any other component and render it.
Since we don’t know its children and properties beforehand, rendering could cause some troubles — for example, properties of children clashing with components’ properties or problems with name and type checking since this component needs to accept any property.
We solved this problem by introducing a special property — “dependencies”.
<WidgetWithExpensiveRender dependencies=[somePropA, somePropB]>
<Child somePropA somePropB />
The shouldComponentUpdate of the WidgetWithExpensiveRender compares not only its own properties but also the dependency property and checks for changes. This way we will render WidgetWithExpensiveRender and its children only when something changes.
Sorting these dependencies by hand would be tedious and error-prone. Fortunately, we developed an automatic mechanism in the OutSystems Platform compiler which collects and registers all the required dependencies. However , we don’t do this for every component because injecting dependencies can introduce runtime scalability issues.
Adding Virtual Reactivity
Compared with native apps, we were still lagging a bit behind. Since we were running out of React “magic” powers, we went for another approach — components virtualization. Components virtualization allows us to render only what’s visible in the viewport, giving OutSystems applications the desired native like performance. Since this is kind of a complex topic, I’ll leave the details for another blog post.
React is a performant framework that we really love but, in order to take your apps performance to a native-like level, you have to know how to take advantage of all its capabilities and go deep in React’s inner workings. With OutSystems Platform 10 we abstracted the use of keys, selective component update, fast comparison, metrics and virtual reactivity to deliver faster and optimized mobile apps.
Editor’s Note: João Neves is a React expert who lives and breathes performance. He’s been relentlessly cutting milliseconds from mobile apps built with OutSystems.