How to Use Classes and Sleep at Night

  • Classes obscure the prototypal inheritance at the core of JS.
  • Classes encourage inheritance but you should prefer composition.
  • Classes tend to lock you into the first bad design you came up with.
  • Resist making classes your public API. (Of course exporting React components is an exception as they aren’t used directly.) You can always hide your classes behind the factory functions. If you expose them, people will inherit from them in all sorts of ways that make zero sense to you, but that you may break in the future. Avoiding breaking people’s classes is hard because they might use the method names you want to use in the future versions, they might read your private state, they might put their own state onto your instances, and they might override your methods without calling super which may work for a while if they’re lucky but break later. That said, when there isn’t back-and-forth interaction between the base class and the user’s derived classes, such as in case of React components, exposing a base class may very well be a valid API choice.
  • Don’t inherit more than once. Inheritance can be handy as a shortcut, and inheriting once is um-kay, but don’t go deeper. The problem with inheritance is that the descendants have too much access to the implementation details of every base class in the hierarchy, and vice versa. When the requirements change, refactoring a class hierarchy is so hard that it turns into a WTF sandwich with traces of outdated requirements. Instead of creating a class hierarchy, consider creating several factory functions. They may call each other in chain, tweaking the behavior of each other. You may also teach the “base” factory function to accept a “strategy” object modulating the behavior, and have the other factory functions provide it. Regardless of the approach you choose, the important part is to keep inputs and outputs explicit at every step. “You need to override a method” is not an explicit API and is hard to design well, but “you need to provide a function as an argument” is explicit and helps you think it through.
  • Don’t make super calls from methods. If you override a method in a derived class, override it completely. Tracing a sequence super calls is like following a series of notes hidden around the world by a movie villain. It’s only fun when you watch someone else doing it. What if you really need to transform the result of the super method, or perform it before or after doing something else? See the previous point: turn your classes into the factory functions and keep the relationships between them very explicit. When your only tools are parameters and return values, it’s easier to discover the right balance of responsibilities. The intermediate functions can have different (and more fine-grained) interfaces than the “top-level” and “bottom-level” functions. Classes don’t easily provide such mechanisms because you can’t designate a method “for the use of base class only” or “for the use of derived class only”, but functional composition makes it natural.
  • Don’t expect people to use your classes. Even if you choose to provide your classes as a public API, prefer duck typing when accepting inputs. Instead of instanceof checks, assert the existence of the methods you plan to use, and trust the user to do the right thing. This will make userland extensions and later tweaks easier, as well as eliminate the issues with iframes and different JavaScript execution contexts.
  • Learn functional programming. It will help you not think in classes, so you won’t be compelled to use them even though you know their pitfalls.

So What About React Components?

I hope that the rules above show why this is a terrible idea:

import { Component } from 'react';class BaseButton extends Component {
componentDidMount() {
console.log('hey');
}
render() {
return <button>{this.getContent()}</button>;
}
}
class RedButton extends BaseButton {
componentDidMount() {
super.componentDidMount();
console.log('ho');
}
getContent() {
return 'I am red';
}
render() {
return (
<div className='red'>
{super.render()}
</div>
);
}
}
import { Component } from 'react';class Button extends Component {
componentDidMount() {
console.log('hey');
}
render() {
return (
<div className={this.props.color}>
<button>{this.props.children}</button>
</div>
);
}
}
class RedButton extends Component {
componentDidMount() {
console.log('ho');
}
render() {
return (
<Button color='red'>
I am red
</Button>
);
}
}
  • You can use class in your JS if you don’t inherit twice and don’t use super.
  • Prefer to write React components as pure functions when possible.
  • Use ES6 classes for components if you need the state or lifecycle hooks.
  • In this case, you may only extend React.Component directly.
  • Give your feedback to the React team on the functional state proposals.

--

--

Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dan Abramov

Dan Abramov

72K Followers

Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.