React is an abstraction over the DOM and as any abstraction, it has its costs and limitations that you may hit sooner or later. Understanding and being able to overcome such limitations are important parts of working with an abstraction. There is a belief that React is fast out of the box and to some degree it is true — most of the time building user interfaces with React you may not think of optimizing for performance. Sometimes though, to achieve better performance (and better user experience) one has to think out of the box. I’d like to share some techniques I’ve been using when working with React. Don’t optimize prematurely! A word of warning before we even begin. No matter what you do, please don’t optimize prematurely for performance. This means, don’t do anything until you have evidence that you have a performance problem. With React, optimizing too much might lead to weird bugs. Every performance optimization process should look like this: Realise there is a performance issue Measure using DevTools and analyze to find the bottleneck Work around the issue Test again to confirm an improvement Goto 2 if needed By the way, that integrates nicely with Chrome’s DevTools and makes it easier to locate slow components in the render tree. React 15.4 introduces the new performance tool Now let’s dive in into some common and not so common React-related performance optimizations techniques I’ve been using on various projects. To shouldComponentUpdate or not? Many are familiar with React’s life-cycle method called . This method returns a Boolean value depending on which React will skip the render method call for the component that implements this method. shouldComponentUpdate As I started working with React a few years ago I naïvely assumed that the library will automatically optimize rendering by not calling render if state and props didn’t change. As a matter of fact, by default this method doesn’t do anything and thus React.js always triggers a re-render when you call or a component receives new props. this.setState Implementing the method is probably the simplest way to make a slow component faster but this method has some pitfalls. By bailing out of rendering high in the component tree, you might miss a required re-render further down the tree since it will skip rendering for all child components of the component where the method is defined. The most common implementation like that shallowly compares next and current and also can be tricky: shouldComponentUpdate shouldPureComponentUpdate state props It won’t compare deeply nested objects (and comparing deeply nested objects is slow — this is why you should consider using immutable data structures) Passing new instances of callback functions via will also make this function always return . Tip: use ESLint with to catch it. props true eslint-plugin-react Checks aren’t free. Doing lots of checks can slow down your application. In practice this means that in most cases it should only be used for: Pure components that use simple props (no deep objects or arrays in props) “leaf”-components or components located deep in the rendering tree. That’s why I think that even before you start implementing the shouldComponentUpdate on your classes (or, if you prefer functional components, composing with ), you should analyze and find out what component makes the app slower. pure HoC Move expensive code to a higher level component If you have some expensive calculation of derived data in the render method you might reduce the number of calls by an order of magnitude by off-loading these calculations to a higher-level component or the result. Using libraries like can be a huge help. memoizing reselect During my work on I was able to improve the hover performance for service metrics graphs by: https://status.postmarkapp.com Splitting the visualization and the overlay info into separate components Off-loading the expensive data transformation to the wrapper component Implementing methods for both visualization and overlay components shouldComponentUpdate Using immutable data structures so the comparison is less expensive ServiceMetric Component hover example Often though, one of the slowest parts of applications I’ve worked on was triggering DOM manipulation following user input. Reacting to scroll or mouse events is a great way to slow down your application. By nature such events can fire at a very high rate. And since browser only has 16ms to do all the work to run at 60fps, reacting to each of those events can completely block your JS application. Often the pattern is used to prevent these drops in performance. They indeed reduce the number of callback calls but at the same time also make our UI feel less responsive to user input. Can we still react to mouse events and stay within the performance budget? debounce Synchronized scroll component example To illustrate the process, I’ve built the synchronized scroll component similar to what I made for the post editor: Netlify CMS The component should keep the scrolling positions of the two panes in sync. Since the height of the content of each pane can be different, the component needs to scroll the panes at different speeds. It should also work with any other components and be easy to integrate regardless of your application structure. Don’t abuse this.setState One of the common mistakes in React applications I’ve seen often is the usage of the method for storing internal DOM state in the component. this.setState If you don’t use something in , it shouldn't be in the state. render() Now consider the following example which was the first implementation of SyncronizedPane Component I came up with: It’s tempting to put all the state into the (because of its name, I guess). The problem with this is that each time you call to change it, React will re-render the whole consequent tree of elements that might cost a lot of CPU time. this.state this.setState Slide from https://speakerdeck.com/vjeux/react-rally-animated-react-performance-toolbox The question is: do you to pass the value as a prop down the component’s tree using React life-cycle? Many forget that you still can store the arbitrary state in an instance variable. In the example above, doing really need scrollTop will not trigger a re-render. But how do we update the scroll position of the underlying component then? The trick here is to do it manually. you might shout at this moment. — What?! That’s not declarative code anymore! — No, it is not! And neither it is idiomatic React! A nice thing about React is that by using the context you still can write imperative code or access the DOM directly but hide it from other components so that the rest of application’s code will still remain clean and declarative. Context allows creating child-parent relationships between components. This means our children components can gain access to some state or even methods of the parent component. Direct DOM Manipulation So, taking the previous example, we could re-write it like (simplified version): So what’s happening here? The ScrollContainer component implements register / unregister methods that are meant to add / remove panes and attach / detach event listeners The ScrollPane component does very little now: it only calls register and unregister at mount and unmount time Each time one of the panes fires an event, the callback gets triggered, which calculates and sets new positions for all of the panes. onScroll scrollTop See how we completely skip using and passing props down the tree. This allows updating the scroll positions of the DOM nodes without triggering the CPU-intensive virtual DOM operations of React. Besides that, a few more interesting things happening there: this.setState We now can use ScrollPane at any place in our application and not just as a child of ScrollContainer. All ScrollPane components register themselves in the container component by passing references to their DOM nodes. This makes the calculation and manipulation of the scrollTop properties trivial. We can now have any number of “panes” with the synchronized scroll. You can find the full code for the working component on GitHub: and a working demo and documentation here: . https://github.com/okonet/react-sync-scroll http://react-sync-scroll.netlify.com/ Turns out, this technique is also being used in React Native’s . See the slides of the presentation by @vjeux: Animated https://speakerdeck.com/vjeux/react-rally-animated-react-performance-toolbox Conclusion There are many best practices and patterns that can lead to a better application architecture and you should definitely follow them until the user experience of the application starts suffering. Sometimes, doing things in a less idiomatic way or not always following the “React way” can lead to a better user experience. Finding a balance between good performance and code maintainability is tricky but this is a part of every UI-developer’s job. Ultimately, we build software not for the sake of following patterns, but for people. Related reading and watching: Performance Engineering With React by Cheng Lou On the Spectrum of Abstraction by Spencer Ahrens React Native: Building Fluid User Experiences Slides from by @vjeux Animated! Thanks for editing and for the review. Karl Horky Max Stoiber
Share Your Thoughts