How-does-react-tell-a-class-from-a-function

React: How does a Class distinguish from a Function?

It took 2h+… If you think it’s easy to read then I’ve done you a little service

React calls the difference between the two

Let’s look at the new Greeting component of function type:

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

React also supports defining it as a class type:

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

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

When you want to render the
component, you don’t have to worry about how it is defined:

// Class or function
<Greeting />
Copy the code

However, React itself would be considered different.

If Greeting were a function, React would simply call it directly:

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

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

But if Greeting were a class, React would need to instantiate it with new and then call the Render method on the instance:

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

In both cases, the React goal is to get the render node (in this case,

Hello
), but the exact steps depend on the type of Greeting.

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

In fact, this article is more about JavaScript than React. If you’re curious about how React works a certain way, let’s explore how it works.

It has been a long journey of discovery. This article doesn’t have much information about React itself, which we’ll discussnew.this.class.arrow function.prototype.__ proto__.instanceofThese concepts, and the mechanics of how these things work in JavaScript. Fortunately, when you’re just using React, you don’t have to think about it that much. But if you dig into React…

(If you really just want the answer, drag it to the end of the article.)

Why call it differently?

First, we need to understand the importance of treating classes and functions differently. Notice how we use the new operator when calling the class:

// 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 what new does in Javascript:


Before ES6, Javascript did not have the concept of class. However, you can use pure functions to show a similar pattern to class. Specifically, you can use new to call functions that resemble class constructors to behave like class

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

var fred = new Person('Fred'); // βœ… Person {name: 'Fred'}
var george = Person('George'); // πŸ”΄ will not work as scheduled
// You can still write this code today! Try it out in 'DevTools'.
Copy the code

If you do not decorate Person(‘Fred’) with new, this inside Person will point to window or undefined. The result is code that crashes or is as stupid as assigning window.name.

Adding new before the call says, “Hey JavaScript, I know Person is just a function, but let’s pretend it’s a class constructor. Create a {} object and point this to it within the Person function so THAT I can assign something like this.name. And return that object to me.”

That’s what the new operator does.

var fred = new Person('Fred'); // in 'Person', the same object is' this'
Copy the code

The new operator also makes the above Fred object usable with anything on Person.prototype.

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 people used to emulate classes in JavaScript.

What you can see is that JavaScript already has new. Class, however, was a late addition. To make our intentions clearer, let’s rewrite the code:

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

It is important to understand developer intentions in language and API design.

If you write a function, JavaScript can’t guess whether you mean to be called directly, like alert(), or act as a constructor, like new Person(). Forgetting to add new can lead to confusing execution results.

The class syntax lets us explicitly tell Javascript: “This isn’t just a function – it’s a class, and it has a constructor.” If you forget to use new when calling class, JavaScript throws an exception:

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 unexpected results such as this.name being treated as window.name instead of george.name.

However, this means React needs to use new before calling any classes. It can’t just be called directly as a normal 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

React needs to distinguish between Class and Function… .

How to solve React style

Compilation tools such as Babel are a problem solver

As we explore how React might solve this problem, we need to consider that most people use compilers like Babel for browser compatibility (e.g., escaping classes, etc.), so we need to consider compilers in our design.

In earlier versions of Babel, classes could be called without new. But this has been fixed by generating 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');   // πŸ”΄ Can’t call class as a function
Copy the code

You might see similar code in a package file, which is what the _classCallCheck function does. (You can reduce the bundle size by setting “loose Mode” without checking, but this can complicate the code’s eventual conversion to a true native class.)

By now, you should have a general idea of the difference between calling something with new and not using new:

new Person() Person()
class βœ… this is a Person instance πŸ”΄ TypeError
function βœ… this is a Person instance 😳 this is window or undefined

This difference is an important reason why React needs to call components correctly. If your component is defined as a class, React needs to use new when calling it.

Can React tell if something is a class?

It’s not that easy! Even though we can distinguish between classes and functions in JavaScript ES6, this still doesn’t apply to classes after they are processed by tools like Babel. Because to the browser, they’re just functions (classes handled by Babel).


Okay, maybe React could use new every time it’s called? Unfortunately, it doesn’t always work.

Anomaly 1:

As a general function, calling them with new provides them with an object instance as this. It is ideal for functions written as constructors (Person, above), but it introduces chaos to function components:

function Greeting() {
  // We don't want "this" to be an instance of either case here
  return <p>Hello</p>;
}
Copy the code

While this can be tolerated, there are two other reasons to kill the idea of using new all the time.

Abnormal Case TWO:

The first is that the arrow function (when not compiled by Babel) invalidates the new call, and calling the arrow function with new throws an exception

const Greeting = () => <p>Hello</p>;
new Greeting(); // πŸ”΄ Greeting is not a constructor
Copy the code

This is intentional and follows the design of the arrow function. One of the main advantages of arrow functions is that they do not have their own this binding – instead this is bound to the nearest function body.

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

Okay, so the arrow function doesn’t have its own this. This means that arrow functions cannot be constructors!

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

Therefore, JavaScript does not allow you to call arrow functions with new. If you do this, you will only make mistakes. This is similar to the way JavaScript does not allow classes to be called without new.

This is nice, but it also foiled our plan to add new before all function calls. React cannot call new in all cases because it would break the arrow function! We can try to identify the arrow function by missing prototype:

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

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

Abnormal Case 3:

Another reason we can’t always use new is that doing so does not support returning strings or other primitive types.

function Greeting() {
  return 'Hello';
}

Greeting(); / / βœ… 'Hello'
new Greeting(); / / 😳 Greeting {}
Copy the code

This is again related to the strange performance of New’s design. As we saw earlier, new tells the JavaScript engine to create an object, create it inside the function, and then take that object as the result of new. However, JavaScript also allows functions called with new to override the return value of new by returning some other object. This can be useful for patterns like object pooling:

// 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 === c
Copy the code

However, if the return value of the function is not an object, new ignores the return value of the function entirely. If you return a string or a number, it is as if nothing had been returned.

function Answer() {
  return 42;
}

Answer(); / / βœ… 42
new Answer(); / / 😳 Answer {}
Copy the code

When a function is called with new, the original return value (such as a number or string) cannot be read from the function. Therefore, if React always uses new, it will not be able to support functions (components) that return strings.

This is unacceptable, so we have to find another way.

The solution

What have we learned so far? React needs to call the class with new (compatible with Babel), but it cannot use new when it needs to call regular functions or arrow functions (compatible with Babel). And there’s no reliable way to tell them apart. If we can’t solve a general problem, can we solve a more specific problem?

When defining Component as a class, you might want to inherit react.componentusing its built-in methods (such as this.setstate ()). So can we only detect subclasses of React.Component instead of trying to detect all classes?

Spoiler alert: This is exactly what React does.

prototype 与 __proto__

A common way to determine if Greeting is the React Component class is to test Greeting. Prototype instanceof react.component.ponent:

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 JavaScript prototypes.

You may be familiar with prototype chains. Every object in JavaScript may have a “prototype.” When we write Fred.sayhi () but the Fred object does not have the sayHi attribute, we look for the sayHi attribute on Fred’s prototype. If we don’t find it there, we’ll look at the next prototype in the chain — the prototype of Fred’s prototype. And so on.

Confusingly, the prototype property of a class or function does not point to the prototype of that 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 the prototype chain “is more like __proto __. __ proto __. __ proto__ rather than a prototype. The prototype. The prototype. It took me many years to learn that.

The __proto__ of all objects pointing to the Prototype function of their constructor or the prototype property of a class is such a thing

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 looks up 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

In fact, unless you’re debugging something related to the prototype chain, you rarely need to modify __proto__ directly in your code. If you want to add something to Fred.__ proto__, you should put it on Person.prototype. That’s how it was originally designed.

Even browsers should not expose __proto__ attributes because the prototype chain is designed as an internal concept. But some browsers added __proto__, and eventually it was barely standardized.

I still find it confusing that “Prototype properties don’t give you a value” (e.g., Fred. Prototype is undefined because Fred isn’t a function). Personally, I think this is the biggest reason that even experienced developers misunderstand JavaScript prototypes.

Extends with prototype chain

It’s a bit long, isn’t it? Don’t give up! Now that we’re 80% of the way through, let’s move on

We know that when we say call obj.foo, JavaScript actually looks for foo in obj, obj. __ proto__, obj. __ proto.__ proto__, and so on.

The stereotype chain mechanism is more complex for classes, but extends makes it perfect for classes. This is how instances of the React class access 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, the __protp__ chain of a class instance mirrors the inheritance of the copied class:

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

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

So two chains (inheritance chain prototype chain)

Instanceof judgment mode

Since the __proto__ chain mirrors the class’s inheritance, we can tell if Greeting inherits react.component from the prototype chain:

// `__proto__` chain
newThe Greeting () - > the Greeting. The prototype// πŸ•΅οΈ We start here- > React.Com ponent. Prototype/ / βœ… Found it!
      β†’ Object.prototype
Copy the code

Conveniently, x instanceof Y is the same search principle. It looks for y.prototype in the x.__ proto__ chain.

Typically, it is used to determine if something is an instance of a 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

But it can also be used to determine whether a class inherits from another class:

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

This is how we determine whether something is a React component class or a function in general.

React Judgment mode

But that’s not what React does. 😳

One pitfall of the Instanceof solution is that when there are multiple copies of React on the page, the component we are examining may inherit react.componentfrom another Copy of React, and the instanceof approach becomes invalid. Mixing multiple copies of React in a project is not a good idea, but we should try to avoid this legacy problem. (Using Hooks, we might need to force deduplication.)

Another possible manipulation is to check if the render method exists on the prototype. However, it was not clear at the time how the component apis would be transformed. Every judgment operation has a cost and we don’t want to add more than one operation. This also doesn’t work if render is defined as an instance method (such as using class attribute syntax).

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

Originally the logo was on the React.Component base class itself:

// 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 class implementations we want to judge don’t copy static properties (or set __proto__), so flags are lost.

This is a sign of the React to the move to React.Com ponent. The cause of 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 there is to see how React determines class.


Now used in React is the isReactComponent flag check.

If you do not extend react.componentreact will not find the isReactComponent on the prototype and will not treat the component as a class. Now you know why Cannot call a class as a function the most popular answer to the question is to add extends react.ponent. Finally, add a prototype. The render, but the prototype. IsReactComponent warned not to be there.

The actual solution was pretty simple, but I spent a lot of time explaining why React ended up with this solution and what the alternatives were. You may find this process a bit verbose,

In my experience, this happens a lot with development library apis. To make the API easy to use, developers need to consider language semantics (possibly, for multiple languages, including future directions), runtime performance, compatibility with compile situations, status of the complete architecture and packaged solutions, early warnings, and many other things. The end result may not be elegant, but it has to be practical.

If the final API is successful, the user never has to think about the process. Instead, they just need to focus on building applications.

But if you’re also curious…… It’s interesting to see why.