[ ] How does React distinguish Class through a function?

[ ] How does React distinguish Class through a function?


How Does React Tell a Class from a Function?

December 02, 2018 16 min read

Consider this Greetingcomponent which is defined as a function:

React also supports defining it as a class:

(Until recently , that was the only way to use features like state.)

When you want to render a <Greeting/>, you don't care how it's defined:

But React itself cares about the difference!

If Greetingis a function, React needs to call it:

But if Greetingis a class, React needs to instantiate it with the newoperator and then call the rendermethod on the just created instance:

In both cases React's goal is to get the rendered node (in this example, <p>Hello</p>). But the exact steps depend on how Greetingis defined.

So how does React know if something is a class or a function?

Just like in my previous post , you don't need to know this to be productive in React. I didn't know this for years. Please don't turn this into an interview question. In fact, this post is more about JavaScript than it is about React.

This blog is for a curious reader who wants to know why React works in a certain way. Are you that person? Then let's dig in together.

IS A Long Journey This. Buckle up. This POST does not have have much the About Information React Itself, But WE'll Go through some Aspects of new, this, class, arrow Functions, prototype, __proto__, instanceof, and How Things Work Together Those in JavaScript. Luckily, you don't need to think about those as much when you use React. If you're implementing React though

(If you really just want to know the answer, scroll to the very end.)

1. we need to understand why it's important to treat functions and classes differently. Note how we use the newoperator when calling a class:

Let's get a rough sense of what the newoperator does in JavaScript.

In the old days, JavaScript did not have classes. However, you could express a similar pattern to classes using plain functions. Concretely, you can use any function in a role similar to a class constructor by adding newbefore its call:

You can still write code like this today! Try it in DevTools.

If you called Person('Fred') without new , thisinside it would point to something global and useless (for example, windowor undefined). So our code would crash or do something silly like setting window.name.

By adding newbefore the call, we say: Hey JavaScript, I know Personis just a function but let's pretend it's something like a class constructor. Create an {}object and point thisinside the Person function to that object so I can assign stuff like this.name. Then give that object back to me."

That's what the newoperator does.

The newoperator also makes anything we put on Person.prototypeavailable on the fredobject:

This is how people emulated classes before JavaScript added them directly.

So newhas been around in JavaScript for a while. However, classes are more recent. They let us rewrite the code above to match our intent more closely:

Capturing developer's intent is important in language and API design.

If you write a function, JavaScript can't guess if it's meant to be called like alrt()or if it serves as a constructor like new Person(). Forgetting to specify newfor a function like Personwould lead to confusing behavior.

Class syntax lets us say: This isn't just a function it's a class and it has a constructor . If you forget newwhen calling it, JavaScript will raise an error:

This helps us catch mistakes early instead of waiting for some obscure bug like this.namebeing treated as window.nameinstead of george.name.

However, it means that React needs to put newbefore calling any class. It can't just call it as a regular function, as JavaScript would treat it as an error!

This spells trouble.

Before we see how React solves this, it's important to remember most people using React use compilers like Babel to compile away modern features like classes for older browsers. So we need to consider compilers in our design.

In early versions of Babel, classes could be called without new. However, this was fixed by generating some extra code:

You might have seen code like this in your bundle. That's what all those _classCallCheckfunctions do. (You can reduce the bundle size by opting into the loose mode without no checks but this might complicate your eventual transition to real native classes.)

By now, you should roughly understand the difference between calling something with newor without new:

new Person() Person()
class thisis a Personinstance TypeError
function thisis a Personinstance thisis windoworundefined

This is why it's important for React to call your component correctly. If your component is defined as a class, React needs to use newwhen calling it.

So can React just check if something is a class or not?

Not so easy! Even if we could tell a class from a function in JavaScript , this still wouldn't work for classes processed by tools like Babel. To the browser, they're just plain functions. Tough luck for React.

Okay, so maybe React could just use newon every call? Unfortunately, that doesn't always work either.

With regular functions, calling them with newwould give them an object instance as this. It's desirable for functions written as constructor (like our Personabove), but it would be confusing for function components:

That could be tolerable though. There are two other reasons that kill this idea.

The first reason why always using newwouldn't work is that for native arrow functions (not the ones compiled by Babel), calling with newthrows an error:

This behavior is intentional and follows from the design of arrow functions. One of the main perks of arrow functions is that they don't have their own thisvalue instead, thisis resolved from the closest regular function:

Okay, so arrow functions don't have their own this. But that means they would be entirely useless as constructors!

Therefore, JavaScript disallows calling an arrow function with new. If you do it, you probably made a mistake anyway, and it's best to tell you early. This is similar to how JavaScript doesn't let you call a class without new .

This is nice but it also foils our plan. React can't just call newon everything because it would break arrow functions! We could try detecting arrow functions specifically by their lack of prototype, and not new just them:

But this wouldn't work for functions compiled with Babel. This might not be a big deal, but there is another reason that makes this approach a dead end.

Another reason we can't always use newis that it would preclude React from supporting components that return strings or other primitive types.

This, again, has to do with the quirks of the newoperator design. As we saw earlier, newtells the JavaScript engine to create an object, make that object thisinside the function, and later give us that object as a result of new.

However, JavaScript also allows a function called with newto override the return value of newby returning some other object. Presumably, this was considered useful for patterns like pooling where we want to reuse instances:

However, newalso completely ignores a function's return value if it's not an object. If you return a string or a number, it's like there was no returnat all.

There is just no way to read a primitive return value (like a number or a string) from a function when calling it with new. So if React always used new, it would be unable to add support components that return strings!

That's unacceptable so we need to compromise.

What did we learn so far? React needs to call classes (including Babel output) with new but it needs to call regular functions or arrow functions (including Babel output) without new . And there is no reliable way to distinguish them.

If we can't solve a general problem, can we solve a more specific one?

When you define a component as a class, you'll likely want to extend React.Componentfor built-in methods like this.setState(). Rather than try to detect all classes, can we detect only React.Componentdescendants?

Spoiler: this is exactly what React does.

Perhaps, the idiomatic way to check if Greetingis a React component class is by testing if Greeting.prototype instanceof React.Component:

I know what you're thinking. What just happened here?! To answer this, we need to understand JavaScript prototypes.

You might be familiar with the prototype chain . Every object in JavaScript might have a prototype . When we write fred.sayHi()but fredobject has no sayHiproperty, we look for sayHiproperty on fred's prototype. If we don't find it there, we look at the next prototype in the chain fred 's prototype's prototype. And so on.

Confusingly, the prototypeproperty of a class or a function does not point to the prototype of that value. I'm not kidding.

So the prototype chain is more like __proto__.__proto__.__proto__than prototype.prototype.prototype. This took me years to get.

What's the prototypeproperty on a function or a class, then? It's the __proto__given to all objects newed with that class or a function!

And that __proto__chain is how JavaScript looks up properties:

In practice, you should almost never need to touch __proto__from the code directly unless you're debugging something related to the prototype chain. If you want to make stuff available on fred.__proto__, you're supposed to put it on Person.prototype. At least that's how it was originally designed.

The __proto__property wasn't even supposed to be exposed by browsers at first because the prototype chain was considered an internal concept. But some browsers added __proto__and eventually it was begrudgingly standardized (but deprecated in favor of Object.getPrototypeOf()).

And yet I still find it very confusing that a property called prototypedoes not give you a value's prototype (for example, fred.prototypeis undefined because fredis not a function). Personally, I think this is the biggest reason even experienced developers tend to misunderstand JavaScript prototypes.

This is a long post, eh? I'd say we're 80% there. Hang on.

We know that the when say WE obj.foo, JavaScript Actually looks for fooin obj, obj.__proto__, obj.__proto__.__proto__, and SO ON.

With classes, you're not exposed directly to this mechanism, but extendsalso works on top of the good old prototype chain. That's how our React class instance gets access to methods like setState:

In other words, when you use classes, an instance's __proto__chain mirrors the class hierarchy:

2 Chainz.

Since the __proto__chain mirrors the class hierarchy, we can check whether a Greetingextends React.Componentby starting with Greeting.prototype, and then following down its __proto__ chain:

Conveniently, x instanceof Ydoes exactly this kind of search. It follows the x.__proto__chain looking for Y.prototypethere.

Normally, it's used to determine whether something is an instance of a class:

But it would work just as fine to determine if a class extends another class:

And that check is how we could determine if something is a React component class or a regular function.

That's not what React does though.

One caveat to the instanceofsolution is that it doesn't work when there are multiple copies of React on the page, and the component we're checking inherits from another React copy's React.Component. Mixing multiple copies of React in a single project is bad for several reasons but historically we've tried to avoid issues when possible. (With Hooks, we might need to force deduplication though.)

One other possible heuristic could be to check for presence of a rendermethod on the prototype. However, at the time it wasn't clear how the component API would evolve. Every check has a cost so we wouldn't want to add more than one . This would also not work if renderwas defined as an instance method, such as with the class property syntax.

So instead, React added a special flag to the base component. React checks for the presence of that flag, and that's how it knows whether something is a React component class or not.

Originally the flag was on the base React.Componentclass itself:

However, some class implementations we wanted to target did not copy static properties (or set the non-standard __proto__), so the flag was getting lost.

This is why React moved this flag to React.Component.prototype:

And this is literally all there is to it.

You might be wondering why it's an object and not just a boolean. It doesn't matter much in practice but early versions of Jest (before Jest was Good ) had automocking turned on by default. The generated mocks omitted primitive properties, breaking the check . Thanks, Jest.

The isReactComponentcheck is used in React to this day.

If you don't extend React.Component, React won't find isReactComponenton the prototype, and won't treat component as a class. Now you know why the most upvoted answer for Cannot call a class as a functionerror is to add extends React.Component. Finally, a warning was added that warns when prototype.renderexists but prototype.isReactComponentdoesn't.

You might say this story is a bit of a bait-and-switch. The actual solution is really simple, but I went on a huge tangent to explain why React ended up with this solution, and what the alternatives were.

In my experience, that's often the case with library APIs. For an API to be simple to use, you often need to consider the language semantics (possibly, for several languages, including future directions), runtime performance, ergonomics with and without compile- time steps, the state of the ecosystem and packaging solutions, early warnings, and many other things. The end result might not always be the most elegant, but it must be practical.

If the final API is successful, its users never have to think about this process. Instead they can focus on creating apps.

But if you're also curious... it's nice to know how it works.

Personal blog by Dan Abramov . I explain things with words and code.