I work on Missive, a team email/chat client built with React. It uses a multi-column layout to display conversations. On ⬆︎️⬇ key presses, the app navigates to a new conversation. It needs to do so in as few milliseconds as possible to avoid any user-perceivable delay.
A conversation can include hundreds of comments, emails, and events, all composed of multiple components. To optimize navigation between conversations, we started converting many stateful components into functional ones.
For instance, the
Avatar component went from:
We thought this change would instantly improve performance, because when navigating between conversations in Missive, React unmounts and mounts hundreds of components. You’d think functional components would avoid this mounting / unmounting as well as lifecycle events overhead because they are just functions, but you’d be wrong.
The original React 0.14 release notes confirms this (emphasis mine):
This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.
Bummer. React still does a lot of stuff on functional components that, by nature, will never be used. Precious computing milliseconds lost.
Avatar JSX tags to braces and call the function directly. Like this:
We just eliminated one big
React.createElement call, and all React component lifecycle events! No need to wait for the React team to ship internal optimizations.
To measure the change, I created this benchmark, the results are quite staggering! From just converting a class-based component to a functional one, we get a little 6% speedup. But by calling it as a simple function instead of mounting, we get a ~45﹪ total speed improvement.
As with all benchmarks out there, take those figures with a grain of salt. It was built to reflect Missive’s use case of having to mount/unmount a very high number of components as fast as possible (navigating between conversations).
Directly calling functional components as functions instead of mounting them using
React.createElement is much faster. You can do so today in JSX like this:
Simple. Any obvious gotcha we may have overlooked? Share them in the comments.