Ending the debate on inline functions in React

Dounan Shi
Flexport Engineering
4 min readNov 1, 2017

--

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:

Using an inline function as the onClick callback

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 to this.

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.

Total internal reflection. Photo by Timwether, CC BY-SA 3.0

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.

--

--