Pushing React Further — Reacting fast
In the fall of 2014 we started developing version 10 of the OutSystems platform. In the beginning we did several POCs to evaluate the performance and user experience of the applications we wanted the platform to be able 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. Although not as popular, React’s popularity is increasing and the results we achieved with our POC were promising.
React does quite a nice job when it comes to performance, preventing (but not completely avoiding) web developers from making some 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 way reducing the number of layout computations.
While looking very promising, soon we started facing some challenges as the applications grew in complexity.
Key for performance
Lists were one of the first problems we hit. In our first tests, lists did not show the performance we desired, especially on 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, React will complain (using the Development version) if you don’t add keys to the child components of a list.
Fortunately, for OutSystems developers, the platform will take care of this and will automatically insert the appropriate keys to ensure that list updates perform well.
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, i.e., before its render method is called, React provides a hook — shouldComponentUpdate — to check whether a component should be rendered or not given its new state and properties. Skipping the rendering of a component will also skip the rendering of its descendants resulting in a performance shortcut.
We gave it a try and introduced shouldComponentUpdate for lists and list items, and saw a remarkable increase in performance.
In Platform 10, an OutSystems 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 resort to the equals (===) operator. If you mutate the state or properties of your components, it can be harder to perform 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 is also faster when comparing nested information (common case in OutSystems applications). When something changes in the structures graph, every node up to the structures root is also updated (recreated). So there’s no need to perform deep comparisons because every node in the graph is always updated when any of the descendants change.
Since the beginning, when we opted for React, we started using immutable data-structures although, after some profiling, we saw problems in this area. Check here what we’ve done to address those problems.
Don’t waste time
During Platform 10 development we became big fans of Chrome developer tools and React performance tools. Since performance was one of our main concerns, we used those tools on a day to day basis to ensure everything would work smoothly.
React performance tools helps you identify which components take longer to render and find good candidates to have 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
After introducing all these optimizations, we were still behind the native like performance we aimed.
Having an highly composable UI model like ours introduces some optimization challenges.
Check an example of what we hit below:
<Child somePropA somePropB />
In this example 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, this 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 his own properties but also the dependency property and checks for changes. This way we will only render WidgetWithExpensiveRender and its children when something changes.
Sorting these dependencies by hand could be boring and error-prone, fortunately we developed and introduced an automatic mechanism in the OutSystems Platform compiler which collects and registers all the required dependencies without any developer intervention. We don’t do this for every component because injecting dependencies for every component can introduce runtime scalability problems.
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 big 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 all these techniques to deliver faster and optimized mobile apps.
Editor’s Note: João Neves is a React expert who lives and breathes performance. His been relentlessly cutting milliseconds from mobile apps built with OutSystems.