preface

How Does React Tell a Class from a Function?

By discussing the problem, in this article involves a lot of important concepts in JavaScript as prototype, the prototype chain, such as this, classes, inheritance, by thinking about the problem to a review of the knowledge, is a good way of learning, but if you just want to know the answer to this question, as the author said, directly scroll to the bottom.

Due to my limited level, the translation is not in place, please forgive me.

The body of the

React we can use functions to define a component:

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

You can also use Class to define a component:

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

Before React introduced Hooks, class-defined components were the only way to use functionality like state.

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

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

But React will care about these differences.

If Greeting were a function, React would be called 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 create an instance with the new command and then call the Render method of the created instance:

// 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

How does React distinguish between classes and functions?


This article won’t talk too much about React. We’ll explore some aspects of New,this,class, arrow functions, Prototype,__proto__,instanceof and how they all work together in JavaScript.

First, we need to understand why it is important to distinguish between functions and classes, and note how to call a class using the new command:

// 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 look at what the new command does in JavaScript.

JavaScript didn’t have classes before, but you can use a normal function to simulate classes. Specifically, you can use any function called via new to simulate the class constructor:

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

var fred = new Person('Fred'); / / βœ… Person {name:'Fred'}
var george = Person('George'); / / πŸ”΄ Won 't workCopy the code

Now you can still write like this, try it.

If you call Person(‘Fred’) without using the new command, this will point to window or undefined, and our code will explode or behave strangely like window.name.

By calling a function with the new command, we’re saying, “Hello JavaScript, I know Person is just a normal function but let’s assume it’s a constructor of a class. Create a {} object and pass it inside the Person function as its this so I can do something like this.name, and please return that object to me.”

This is what happens after a function is called with the new command

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

The new command also does something to make the attributes we added to Person.prototype accessible on Fred:

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 you simulated a Class before you added it to JavaScript.

If you define a function, JavaScript is not sure you will call it directly like alert() or as a constructor like new Person(). Forgetting to use the new command to call a function like Person can lead to confusing behavior.

The syntax of Class tells us, “This isn’t just a function, it’s a Class with a constructor.” If you forget to add the new command when calling a Class, JavaScript will throw an error:

et 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 will help us catch bugs early, rather than waiting for obvious bugs such as this.name becoming window.name instead of George. name.

Either way, this means React needs to use the new command to call all classes. It can’t call classes as if they were normal functions. If it did, JavaScript would report 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

That’s the wrong way to write it.


Before we talk about how React works, we need to know that most people use Babel to build React projects. The purpose is to make the latest features used in projects like classes compatible with low-end browsers, so we need to understand how Babel builds.

In earlier versions of Babel, classes could be called using the new command. But it fixes this problem 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');   // πŸ”΄ Can’t call class as a function
Copy the code

You may have seen the above code in a packaged file, which is what _classCallCheck does.

By now, you should have a rough idea of the difference between using the new command and not using it:

That’s why it’s so important that React needs to call components correctly. If you define a component using class, React needs to be called using the new command.

Does React tell if a component is defined by a class?

It’s not that easy, even if we can tell a function from a class:

function isClass(func) {
  return typeof func === 'function' 
    && /^class\s/.test(Function.prototype.toString.call(func));
}
Copy the code

This method won’t work if we use a compiler like Babel, which compiles the class to:

// Class Person {} // Babel is compiled"use strict";

function _classCallCheck(instance, Constructor) { if(! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Person = function Person() {
  _classCallCheck(this, Person);
};
Copy the code

To a browser, they are normal functions.


React functions can be called with the new command. The answer is no.

Using the new command to call a normal function passes an object instance as this. Using the function as a constructor, like the Person command above, is fine, but it’s confusing for functional components:

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

Even if you could, there are two reasons why you shouldn’t.

The first reason: Calling the arrow function with the new command (not compiled by Babel) returns an error

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

This error is intentional and follows the design of the arrow function. The arrow function does not have its own this, which is bound to the parent execution context:

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

If you do not understand the children’s shoes, you can refer to the following article

ES6 tutorial – Arrow functions

Full interpretation of this- This wave can counter-kill

Ok, the arrow function doesn’t have its own this, which means it can’t be used as a constructor:

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

Therefore, JavaScript cannot use the new command to call the arrow function, and if you do, the program will report an error, just as if you did not use the new command to call the class.

This is good, but not good for our plan. React can’t be called with the new command. We can also use the arrow function with no prototype to distinguish between React functions and call them without using the new command:

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

But if you use Babel in your project, it’s not a good idea either, and there’s another reason why it’s a dead end.

This is because function components in React are called with the new command, and the function components return no strings or other basic data types.

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

At this point, we need to know what does the new command actually do?

Calling a constructor through the new operator goes through four phases

  • Create a new object;
  • Point the constructor’s this to the new object;
  • Code that points to the constructor, adding properties, methods, and so on to the object;
  • Returns a new object.

There is a more detailed explanation of this- this wave of energy counter-killing in full view of these contents.

If React only used the new command to call functions or classes, it would not be possible to support components that return strings or other primitive data types, which would definitely not be acceptable.


So far, React needs to use the new command to call classes (including those compiled by Babel) and does not use the new command to call normal functions and arrow functions. There is still no viable way to distinguish between them.

When you use the class (class) declare a component, are you sure you want to inherit React.Com ponent in like this. SetState () method for internal. Rather than trying to tell if a function is a class, we should verify that the class is an instance of react.component.

Here’s what the React does.

Prototype instanceof react.prototype

class A {}
class B extends A {}

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

I guess you’re wondering, what’s going on? To answer this question, we need to understand JavaScript archetypes.

You’re probably already familiar with prototype chains, where every object in JavaScript has a “prototype.”

The following examples and diagrams are from the front End Basics (IX) : a detailed explanation of object orientation, constructors, stereotypes, and prototype chains, which I personally find more illustrative than the original examples

// Declare the constructorfunctionPerson(name, age) { this.name = name; this.age = age; } // Attach the method to the prototype object using the prototye property person.prototype.getName =function() {
    return this.name;
}

var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
console.log(p1.getName === p2.getName); // true

Copy the code

When we want to call the getName method on P1, but P1 itself does not have this method, it will look on the prototype of P1, if not found, we will continue to look on the prototype of the next layer along the prototype chain, that is, the prototype of the prototype in P1… And keep looking until the final null of the prototype chain.

Prototype chain more like __proto__. __proto__. __proto__ rather than a prototype. The prototype. The prototype.

So what is the prototype property of a function or class? It is the object to which the __proto__ attribute of the instance you created in the new command points.

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

This __proto__ chain shows how to look up attributes in JavaScript:

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 actual development code, you don’t need to touch __proto__ at all unless you’re debugging something related to the prototype chain. If you want to add something to a prototype, you should add it to Person.prototype, how about __proto___? Of course, it works, but there are performance issues and compatibility issues with this non-specification, click here for details.

Early browsers did not expose the __proto attribute because prototype classes were an internal concept. Later, some browsers supported them. This was standardized in the ECMAScript2015 specification.


We now know that when accessing obj.foo, JavaScript usually looks for foo in obj like this,obj.__proto__,obj.__proto__.__proto__…

Define a class components, you may not see the prototype chain mechanism, but extends (inheritance) only the grammar of the prototype chain of sugar, the React of component class is such access to the React.Com ponent like setState method.

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, the prototype chain of an instance maps the hierarchy of that class

// 'extend' chain new Greeting β†’ React.Com β†’ Object (= __proto__ The Greeting. Prototype - > React.Com ponent. Prototype - > Object. The prototypeCopy the code

Since the prototype chain maps the class hierarchy, we can start with a greeting. prototype that inherits from react.componentgreeting and work our way down the prototype chain:

/ / ` __proto__ ` chain new Greeting () - > the Greeting. Prototype / / πŸ•΅ ️ We start here - React.Com ponent. Prototype / / βœ… Found it!  - > Object. The prototypeCopy the code

In fact, x instanceof Y does just that, looking for y archetypes along the archetype chain of X.

This is usually used to determine if an instance is an instance of a class:

let greeting = new Greeting();

console.log(greeting instanceof Greeting); // true(πŸ•΅οΈ We start here) //.__proto__ β†’ new prototype (βœ… Found it!) / /. __proto__ - > React.Com ponent. Prototype / /. __proto__ - > Object. The prototype console. The log (the greeting instanceof React.Component); //true/ / the greeting (πŸ•΅ ️ We start here) / /. __proto__ - the greeting. Prototype / /. __proto__ - > React.Com ponent. Prototype (βœ… Found it!) //.__proto__ β†’ Object.prototype console.log(greeting instanceof Object); //true/ / the greeting (πŸ•΅ ️ We start here) / /. __proto__ - the greeting. Prototype / /. __proto__ - > React.Com ponent. Prototype / / .__proto__ β†’ Object. Prototype (βœ… Found it!) console.log(greeting instanceof Banana); //false/ / the greeting (πŸ•΅ ️ We start here) / /. __proto__ - the greeting. Prototype / /. __proto__ - > React.Com ponent. Prototype / / .__proto__ β†’ Object. Prototype (πŸ™… Did not find it!)Copy the code

And it can also be used to check if a class inherits from another class:

console.log(Greeting.prototype instanceof React.Component); / / the greeting / /. __proto__ - the greeting. Prototype (πŸ•΅ ️ We start here) / /. __proto__ - > React.Com ponent. Prototype (βœ… Found it!) / /. __proto__ - > Object. The prototypeCopy the code

This way we can detect whether a component is a function component or a class component.


React didn’t do that.

The author also discussed two kinds of schemes here, omitted here, interested to see the original yo.

React actually adds a tag to the underlying component, react.component.it uses this tag to distinguish whether a component is a class component.

// Inside React class Component {} Component.isReactClass = {}; // We can check it like this class Greeting extends Component {} console.log(Greeting.isReactClass); / / βœ… YesCopy the code

Like the above tags directly add to the base component itself, sometimes there will be a static property loss, so we should add tags 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); / / βœ… YesCopy the code

That’s how React works.

There are a few more paragraphs to follow, please refer to another big brother’s translation at the end of this article.

subsequent

This article is a bit long and involves a lot of knowledge. The final solution seems to be quite simple, but in fact it is not easy to get to this stage. I hope everyone can gain something. In the middle of the translation, I found another person’s translation of this article in a Issues of React. If you are interested, you can click to read it.