Ending the debate on inline functions in React
Optimizing React Rendering (Part 3) — Introducing reflective-bind
Using inline functions in React is convenient but can be contentious because of their perceived impact on performance. Today Flexport is introducing our solution to the debate: a Babel transform called reflective-bind
.
But first, some background…
What is an inline function callback?
An inline function callback is a function that is defined within the render
method of a component and passed down to a child component as a prop. They typically manifest themselves in the form of arrow functions and calls to Function.prototype.bind
:
Why do some people like inline functions?
- Easier to write — no need to define a function handler somewhere else.
- Easier to read and understand — don’t need to hunt down the function definition to see what it does.
- Can close over variables from the parent scope. In the example above, the inline function can reference the
msg
variable. - Using
this
within an arrow function is safe. This lets you avoid the hassle of ensuring that callbacks are bound correctly. In the example above,this.props.onAlert
in the callback has the correct reference tothis
.
Why do other people dislike inline functions?
An inline function in render
allocates a new function instance each time render
is called. Some suggest that this will cause performance issues because the garbage collector will be be invoked more often. However, in Flexport’s React app, the garbage collector penalty is dwarfed by another performance issue caused by inline functions: wasteful re-rendering of pure components.
Recall from Part 1 of this blog series that pure components rely on a shallow equality comparison in shouldComponentUpdate
to determine if any props or state have changed. Since two different function instances are never equal, inline functions will cause the shallow equality comparison to fail, and thus will always trigger a re-render.
As a result, many React developers encourage you to never use inline functions in render. However, others think that avoiding them is premature optimization. So as a React developer, what in the world are you supposed to do?
reflective-bind to the rescue
The answer: freely use inline arrow functions in render, and use our ✨new✨ reflective-bind
Babel transform to eliminate wasteful re-renders with almost no code change.
How it works
The root of the problem is caused by the fact that two different function instances are not equal. But what if there was a reliable way to check the equality of two different function instances?
First, let’s take a look at Function.prototype.bind
. Given a function fn
, it is safe to assume that if fn.bind
is called twice with the same context (thisArg) and arguments, then the two resulting function instances are behaviorally equivalent, or what we call “reflectively equal”.
So how can we tell if two bound functions are reflectively equal? Arjun came up with the brilliant idea of simply storing the original function, context, and remaining arguments as properties on the bound function instance. By comparing these three fields, we can determine if two functions are reflectively equal. This was then implemented by Blake:
Now, the reflective-bind/babel
plugin can simply transform all bind
calls to reflectiveBind
.
So that solves it for bind
, but what about arrow functions? If we can massage them into bind
calls, then we can handle them in a similar way. To do that, the Babel plugin hoists the arrow function to the top level, and replaces it with a call to reflectiveBind
, passing in the hoisted function and the closed over variables as arguments:
With these transforms in place, we can use reflectiveEqual
in our shouldComponentUpdate
to check for function equality.
Performance improvements
Flexport has been using reflective-bind
internally for some time now with some big performance gains. For one of our more complex forms, just turning on the Babel transform reduced the wasted render time from 175ms to 18ms.
However, the performance benefits on your app will vary based on the structure of your app and your use of pure components.
Conclusion
The library has allowed our developers to keep writing code in whatever way they are comfortable with, without compromising the ability to optimize our app. We encourage you to try it out and let us know what you think!
Interested in solving these types of problems? Follow us here or on Twitter to learn more about interesting problems in the world of freight, or if you’re ready to take the next step, we’re hiring (check out our current openings)!
More from this series
Part 4 coming soon.