We Jumped the Gun Moving React Components to ES2015 Class Syntax
ES2015 class syntax for React components degrades developer experience and maintainability with little benefit in return.
With React 15.5.0 React.createClass was officially deprecated. Unfortunately, its replacement component declaration — and new community standard — has a notably worse developer experience, generates harder to use and maintain code, and only gets an unnoticeably-small-in-most-cases performance boost in return.
Every engineer makes tradeoffs. We could have written Netflix’s web UI in raw, unadulterated, vanilla JS, but we chose React because the overhead of the framework was worth the ease of use and safety that came with it.
The key to writing good code is to make the right tradeoffs, and the switch the ES2015 class syntax for React components just doesn’t add up.
In ES2015 with React.createClass
Let’s look at an example component, starting with React.createClass.
The first thing you read, after the component’s name is the propTypes, the defaults, any required context, and the state of the component — essentially everything you need to know about the inputs and defaults is right up front.
If you’re flying solo on a small project you might not notice it as much, but as soon as you’re working with other engineers, or on a codebase with many components, having clear code makes a component’s use easier to understand, modify, and maintain.
One other advantage this declaration has is that everything about the component is contained in the createClass call. If you hit that last closing parenthesis, you’ve seen it all.
In ES2015 with native classes
Here’s that same component written with ES2015 class syntax — as directed in React’s documentation.
You’ll notice that the length of the component grew, but what did we get for those extra lines?
- PropTypes, default values, and context must be attached after the component is defined, burying most of the self-documenting aspects of the component at the end of the file while separating initial state out elsewhere.
- Remembering to call super() is now required or we’ll get a syntax error.
- handleClick must be manually bound in the in the constructor, or bound during usage, or we’ll end up with the incorrect “this” and a runtime error.
- “Slightly better performance in large applications” which appears to be largely dependent on how your application uses components. If you’re transpiling down to ES5, you’re already running a lot more code than you probably expect…
A Slightly Better Future with Proposed Class Fields
There’s currently a Stage 2 proposal with the TC-39 to add a host of class features. Most importantly for React, this covers property initializers which give us a much more concise declaration, regaining some of the readability benefits of React.createClass.
With the proposed syntax, we get to put the self-documenting aspects of the component in a useful place and initial state gets to live nearby as well.
We can use the ES2015 fat arrow to skip the internal binding of “this” and refer to the instance instead.
Unfortunately, this language feature is only in stage 2 so its syntax could change. Doubly unfortunate is that for the foreseeable future we’ll be transpiling this syntax, which negates much of the perf benefit gained by not auto-binding our internal methods.
So it’s better, but we’re still fighting to get back to where we were with plain createClass.
But in the end…
Why are we as a community making this change? What are we, the developers, or our users getting out of the switch to ES2015 classes for React Components?
Is it performance?
- Yes, removing React.createClass makes the React library 2–3k smaller, but the transpiled class code in medium to large projects can overtake that rapidly.
- Skipping autobinding might yield a minor performance benefit in some cases, but again the perf hit for transpiled code is frequently added back in.
Is it developer ease?
Reading through the code examples above, there’s not a case for better usability or maintainability or a clearer split between the React-specific properties of a component definition.
So why are we doing it?
Changes in libraries, frameworks, and everyday code should be for a reason and this change not only doesn’t present a compelling one, but provides several reasons against its adoption.
Followup & Updates
I got some feedback and questions on this piece that I wanted to address in the body, as wise folk tend to skip the comments.
“It’s closer to native JS, so that’s better”
I‘m not fully convinced; it’s no more clear for being native JS, and introduces significant boilerplate, mental overhead, and corner cases. Things like state, propTypes, and context are still magic and necessary, so if anything, having some of them split off into native JS statics removes their overt connection and importance to the library.
“Why does it matter? People will just memorize the necessary bits and move forward.”
To some extent, I agree. We can all adapt and if ES2015 class syntax is a better choice for your group, or it makes it feel like a language you’re more comfortable with, totally go for it! My larger point was that I think the community came to a decision that felt better and newer but was a net negative due to the overall developer experience and maintainability aspects.
Part of my job at Netflix is around developer productivity, so I’m interested in making libraries and frameworks that are the easiest to use and maintain for the largest group of people, while still being open to expand into harder to write/maintain code when performance or constraints demand it. I think having kept React.createClass as the default community standard while opening up ES2015 class syntax as an optional add-in when needed would have been a better choice for the community and the codebases they work with. At very least, I think they should have held off until the class fields proposal landed or entered a later stage with TC-39.
“Does Netflix hate the class syntax?”
I saw a couple places where this post was titled something like “Netflix found that class syntax sucks.” No, we didn’t. I did.
One of the major strengths of engineers at Netflix is our diversity of thought. I couldn’t possibly speak for all of Engineering at Netflix — or even all of the website team that I’m part of.
Part of our culture is setting context instead of demanding control, and the reason I made this post was to spread some of the context, as I see it, to the greater community. We may be past this particular decision, but the same types of decisions and proposals will come up again and again within our own projects or in libraries and frameworks that we care about; having the mindful discussion of what we’re trading in UX and DX will always lead to a better place.