Take a look at the Greeting component defined by function:

function Greeting() {
  return <p>Hello</p>;
}
Copy the code

React also supports class definitions:

class Greeting extends React.Component {
  render() {
    return <p>Hello</p>; }}Copy the code

(Until recently, this was the only method that could use functionality like state.)

When you want to render a
, you don’t care how it is defined:

// Class or function -- whatever.
<Greeting />
Copy the code

But React itself is about considering the difference between the two.

If Greeting were a function, React would call it like this:

// Your code
function Greeting() {
  return <p>Hello</p>;
}

// Inside React
const result = Greeting(props); // <p>Hello</p>
Copy the code

But if Greeting is a class, React needs to instantiate an object with the new operation and then call the Render method of the instance object.

// Your code
class Greeting extends React.Component {
  render() {
    return <p>Hello</p>; }}// Inside React
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
Copy the code

The purpose of both React classes is to get rendered nodes (in this case,

Hello

). But the exact steps depend on how Greeting is defined.

So how does React recognize whether a component is a class or a function?

As in the previous article, you don’t need to know the implementation in React. For years I had no idea. Please don’t use it as an interview question. In fact, this article is more about JavaScript than React.

This article is for those of you who are curious about why React works the way it does. Are you? Let’s dig together.

It’s a long trip. Buckle up. This article doesn’t have much to say about React itself, but we’ll experience some other aspects:new,this,class,arrow function,prototype,__proto__,instanceof, and their dependencies in JavaScript. Fortunately, you don’t have to think too much about React.

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


First, we need to understand why the different processing functions and classes are important. Notice how we use new when class is called.

// If Greeting is a function
const result = Greeting(props); // <p>Hello</p>

// If Greeting is a class
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
Copy the code

Let’s take a quick look at how new works in JavaScript.


In the past, JavaScript didn’t have classes. However, you can use the plain function to approximate it. Specifically, you can create functions by adding new to function as you would to class:

// Just a function
function Person(name) {
  this.name = name;
}

var fred = new Person('Fred'); // ✅ Person {name: 'Fred'}
var george = Person('George'); / / 🔴 Won 't work
Copy the code

You can still do that today! Try it out with development tools.

If you call Person(‘Fred’) without new, this in the method will point to global or null (for example, window or undefined). So our code will either error or unknowingly assign window.name.

To call the method with new, we say: “Hey JavaScript, I know Person is just a function, but let’s pretend it’s a class constructor. Add a {} object, point this in Person to that object, and I can assign it a value like this.name and return the object to me.”

That’s what New does.

var fred = new Person('Fred'); // Same object as `this` inside `Person`
Copy the code

The new operator also applies the things we stored in Person.prototype to the Fred object:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  alert('Hi, I am ' + this.name);
}

var fred = new Person('Fred');
fred.sayHi();
Copy the code

This is how JavaScript emulates classes before they can be added directly.


So new has been in JavaScript for some time. However, class comes later. It allows us to get closer to rewriting the above code as we intended:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert('Hi, I am ' + this.name); }}let fred = new Person('Fred');
fred.sayHi();
Copy the code

For a language and API design, it is important to capture the developer’s intent.

If you write a function, JavaScript can’t guess whether it should be called like alert() or as a constructor like new Person(). Forgetting to add new to function can cause unpredictable things to happen.

The Class syntax allows us to say, “This isn’t just a function – it’s a Class with a constructor.” If you forget to add new to the call, JavaScript will throw an error:

let fred = new Person('Fred');
// ✅ If Person is a function: works fine
// ✅ If Person is a class: works fine too

let george = Person('George'); // We forgot `new`
// 😳 If Person is a constructor-like function: confusing behavior
// 🔴 If Person is a class: fails immediately
Copy the code

This helps us catch errors early, rather than having some elusive bug later, such as this.name being george.name instead of window.name.

However, this also means that React needs to be preceded by new when calling any class. It can’t be called like function because JavaScript will treat it as an error.

class Counter extends React.Component {
  render() {
    return <p>Hello</p>; }}// 🔴 React can't just do this:
const instance = Counter(props);
Copy the code

This can lead to trouble.


Before we look at how React solves this problem, it’s important to remember that most people use a compiler like Babel to compile newer features that are compatible with older browsers. So we need to think about compilers in our design.

In older versions of Babel, class could be called without new. But this was fixed — with some extra code:

function Person(name) {
  // A bit simplified from Babel output:
  if(! (this instanceof Person)) {
    throw new TypeError("Cannot call a class as a function");
  }
  // Our code:
  this.name = name;
}

new Person('Fred'); / / ✅ Okay
Person('George');   // 🔴 Cannot call a class as a function
Copy the code

You’ll probably see this code in the bundle, and that’s what all the _classCallCheck functions do. (You can reduce the bundle size by choosing “loose Mode “without checking, but the native class you end up converting to will cause problems in real development.)


Now you should have a rough idea of the difference between calling with new and without new:

new Person() Person()
class this is a Person instance 🔴 TypeError
function this is a Person instance 😳 this is window or undefined

That’s why it’s important to call your React component correctly. If your component is declared with class, React needs to call it with new.

So React can only determine if it’s class?

It’s not that simple! Even if we can distinguish between classes and functions, this still doesn’t apply to classes processed by tools like Babel. To the browser, they are simply functions. Bad luck for React.


Okay, so maybe React could use new for every call? Unfortunately, this doesn’t always work.

Normal functions, call them with new to get instance objects equivalent to this. This works for functions written as constructors (like Person above). But there are problems with the function component:

function Greeting() {
  // We wouldn’t expect `this` to be any kind of instance here
  return <p>Hello</p>;
}
Copy the code

The situation is tolerable. But there are two reasons to kill the idea.


The first reason is that new does not work with native arrow functions (not compiled by Babel), and calling with new throws an error:

const Greeting = () => <p>Hello</p>;
new Greeting(); // 🔴 Greeting is not a constructor
Copy the code

This is intentional, following the design of the arrow function. The main feature of arrow functions is that they do not have their own this value, which is determined by the general function closest to themselves.

class Friends extends React.Component { render() { const friends = this.props.friends; return friends.map(friend => <Friend // `this` is resolved from the `render` method size={this.props.size} name={friend.name} key={friend.id} /> ); }}Copy the code

Ok, so arrow functions don’t have their own this. But it also means they can’t be constructors.

const Person = (name) = > {
  // 🔴 This wouldn’t make sense!
  this.name = name;
}
Copy the code

Therefore, JavaScript does not allow calling arrow functions with new. If you do, you’re bound to make a mistake. Let you know. This is similar to JavaScript not allowing classes to be called without new.

That was nice, but it also affected our plans. React cannot call all components with new because of the arrow function. We can use the missing prototype to verify the feasibility of the arrow function instead of just using new:

((a)= > {}).prototype // undefined
(function() {}).prototype // {constructor: f}
Copy the code

This does not apply to functions compiled with Babel. That may not be a big deal, but there’s another reason this approach is dead.


Another reason we can’t always use new is that it would prevent React from supporting components that return strings or other primitive data types.

function Greeting() {
  return 'Hello';
}

Greeting(); / / ✅ 'Hello'
new Greeting(); / / 😳 Greeting {}
Copy the code

Again, this has to do with the weird design of the new operator. As we saw earlier, new tells the JavaScript engine to create an object that equals this in function, and then returns the object as the result of new.

However, JavaScript also allows the function of new to override the return value of new by returning some object. Presumably, this is thought to be useful if we want to pool instances for reuse.

// Created lazily var zeroVector = null; function Vector(x, y) { if (x === 0 && y === 0) { if (zeroVector ! == null) { // Reuse the same instance return zeroVector; } zeroVector = this; } this.x = x; this.y = y; } var a = new Vector(1, 1); var b = new Vector(0, 0); var c = new Vector(0, 0); // 😲 b === cCopy the code

However, if the return value of function is not an object, new ignores the return value entirely. If you return a string or a number, that’s exactly the same as not returning a value at all.

function Answer() {
  return 42;
}

Answer(); / / ✅ 42
new Answer(); / / 😳 Answer {}
Copy the code

When you call function with new, you can’t read the raw data return value (like number or string). It doesn’t support components that return strings.

This is unacceptable, so we have to compromise.


What have we learned so far? React must use new to call class (containing the output of Babel), but it must not use new to call normal function (containing the output of Babel) or arrow functions, and there is no reliable way to distinguish between them.

If we can’t solve the general problem, can we solve the more specific problem?

When you declare a component with class, you may want to extend the built-in methods of react.component. such as this.setstate (). Instead of detecting all classes, can we detect only descendants of React.Com Ponent?

Spoiler alert: This is exactly what React does.


Maybe, if Greeting is a class component, you can check it in a common way by testing Greeting. Prototype instanceof React.Com:

class A {}
class B extends A {}

console.log(B.prototype instanceof A); // true
Copy the code

I know what you’re thinking. What just happened? To answer this question, we need to understand the prototype of JavaScript.

You’ve probably heard a lot about “prototype chains.” In JavaScript, all objects should have a “prototype.” When we write Fred.sayhi () and Fred has no sayHi attribute, we look for “sayHi” in Fred’s archetype. If we can’t find it, we look at the next prototype in the chain — the Fred prototype, and so on.

Inexplicably, a class or functionprototypeattributeWill not beThe prototype pointing to the value. I’m not kidding.

function Person() {}

console.log(Person.prototype); // 🤪 Not Person's prototype
console.log(Person.__proto__); / / 😳 Person 's prototype
Copy the code

So __proto__. __proto__. __proto__ than the prototype. The prototype, the prototype is more like a “prototype chain”. It took me years to understand that.

So what is the function or class prototype property? It is __proto__ provided to all objects passed by class or function new.

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  alert('Hi, I am ' + this.name);
}

var fred = new Person('Fred'); // Sets `fred.__proto__` to `Person.prototype`
Copy the code

And the __proto__ chain is how JavaScript finds attributes:

fred.sayHi();
// 1. Does fred have a sayHi property? No.
// 2. Does fred.__proto__ have a sayHi property? Yes. Call it!

fred.toString();
// 1. Does fred have a toString property? No.
// 2. Does fred.__proto__ have a toString property? No.
// 3. Does fred.__proto__.__proto__ have a toString property? Yes. Call it!
Copy the code

When coding, you rarely need to touch __proto__ in your code unless you are debugging a prototype chain-related error. If you want to add something to fred.__proto__, you should add it to Person.prototype. At least that’s how it was designed.

Initially, the __proto__ attribute should not even be exposed by the browser, because the prototype chain is considered an internal concept. But some browsers added __proto__, and eventually it was grudgingly standardized (deprecated, replaced by Object.getPrototypeof ()).

However, I still find it confusing that a property called prototype does not provide you with that value (e.g., Fred. Prototype becomes undefined because Fred is not a function). For me, I think this is the biggest reason even experienced developers misunderstand JavaScript prototypes.


It’s a long article, isn’t it? I’d say we’re at 80%. Go ahead.

When we write obj.foo, JavaScript actually looks for foo on obj, obj.__proto__, obj.__proto__.__proto__, and so on.

You won’t see this mechanism directly in Class, but extends is also implemented on top of this classic prototype chain. This is why our React class instance gets methods like setState:

class Greeting extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

let c = new Greeting();
console.log(c.__proto__); // Greeting.prototype
console.log(c.__proto__.__proto__); // React.Component.prototype
console.log(c.__proto__.__proto__.__proto__); // Object.prototype

c.render();      // Found on c.__proto__ (Greeting.prototype)
c.setState();    // Found on c.__proto__.__proto__ (React.Component.prototype)
c.toString();    // Found on c.__proto__.__proto__.__proto__ (Object.prototype)
Copy the code

In other words, when you use a class, an instance’s __proto__ chain “copies” the class structure:

// `extends` chainThe Greeting - ponent - > React.ComObject (implicitly)

// `__proto__` chain
newThe Greeting () - > the Greeting. Prototype - > React.Com ponent. Prototype - >Object.prototype
Copy the code

The two chains.


Since the __proto__ chain reflects the structure of the class, we can start with greeting. prototype and check down the __proto__ chain to see if a Greeting extends react.component:

/ / ` __proto__ ` chain new Greeting () - > the Greeting. Prototype / / 🕵 ️ We start here - React.Com ponent. Prototype / / ✅ Found it!  - > Object. The prototypeCopy the code

In short, X instanceof Y does exactly this kind of search. It follows the x.__proto__ chain to find y.prototype.

Typically, this is used to determine if something is an instance of class:

let greeting = new Greeting();

console.log(greeting instanceof Greeting); // true
// greeting (🕵️‍ We start here)
//.__proto__ → Greeting. Prototype (✅ Found it!)
/ /. __proto__ - > React.Com ponent. Prototype
/ /. __proto__ - > Object. The prototype

console.log(greeting instanceof React.Component); // true
// greeting (🕵️‍ We start here)
/ /. __proto__ - the Greeting. The prototype
/ /. __proto__ - > React.Com ponent. Prototype (✅ Found it!)
/ /. __proto__ - > Object. The prototype

console.log(greeting instanceof Object); // true
// greeting (🕵️‍ We start here)
/ /. __proto__ - the Greeting. The prototype
/ /. __proto__ - > React.Com ponent. Prototype
//.__proto__ → object. prototype (✅ Found it!)

console.log(greeting instanceof Banana); // false
// greeting (🕵️‍ We start here)
/ /. __proto__ - the Greeting. The prototype
/ /. __proto__ - > React.Com ponent. Prototype
//.__proto__ → object. prototype (🙅‍ Did not find it!)
Copy the code

It can also be used to determine whether one class extends another:

console.log(Greeting.prototype instanceof React.Component);
// greeting
//.__proto__ → Greeting. Prototype (🕵️‍ We start here)
/ /. __proto__ - > React.Com ponent. Prototype (✅ Found it!)
/ /. __proto__ - > Object. The prototype
Copy the code

If something is a class or function React component, we can use this to gauge our thinking.


But that’s not how React works. 😳

One problem is that in React, the component we’re checking might be a copy of react.componentthat inherits from another React component. The Instanceof solution doesn’t work with multiple copies of the React component on the page. Empirically, there are several reasons why remixing React components multiple times is not a good choice in a project, and we should try to avoid it. In Hooks, we may need to enforce the idea of removing duplicates.

Another heuristic is to test for the presence of render methods on prototypes. However, it was not clear at the time how the component API would evolve. We need to add one more test time for each test, and we don’t want to spend more than two times here. And when render is a method defined on an instance (such as the class attribute syntax sugar definition), this approach doesn’t work.

React therefore adds a special flag to the base class component. React checks to see if the React component is a class.

Originally this flag was on the base class react.component.react.ponent:

// Inside React
class Component {}
Component.isReactClass = {};

// We can check it like this
class Greeting extends Component {}
console.log(Greeting.isReactClass); / / ✅ Yes
Copy the code

However, some of the inherited classes we need to determine do not copy static attributes (or set an informal __proto__), so this flag will not work.

This is why the React to transfer this logo to React.Com ponent. The prototype:

// Inside React
class Component {}
Component.prototype.isReactComponent = {};

// We can check it like this
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent); / / ✅ Yes
Copy the code

That’s all for now.

You might be wondering why it’s an object and not a Boolean. There’s nothing to get tangled up in the actual process. In earlier versions of Jest (when Jest was good), automatic locking was enabled by default, and mock data generated by Jest ignored raw data types, rendering React checks ineffective. Thank you very much. Jest.

The isReactComponent detection is still used in React today.

If you don’t extend react.component, React won’t look for the isReactComponent in the prototype and will not treat it as a class component. Now you know why the Cannot call a class as a function error is best answered by using extends react.ponent. Finally, we also added a warning, as the prototype, render exist. But the prototype will warn isReactComponent does not exist.


You might say the story is a bit of a sales pitch. The actual solution was quite simple, but I used a lot of digressions to explain why React ended up using this solution and what the alternatives were.

In my experience, the class library API is often such, in order to make the API easy to use, you often need to consider the semantics of the language (probably for many language, you also need to consider the future direction), run-time performance, ergonomics, and compile time process, ecological, packaging solutions, advance warning, and many other things. The end result may not always be the most elegant, but it has to be practical.

If the final API is viable, its users never need to think about the derivation process. Instead, they can focus on creating apps.

But if you’re still curious… It’s also good to know how it works.

How Does React Tell a Class from a Function?