So schreiben Sie eine React-Komponente ohne Verwendung von Klassen oder Hooks

Mit der Veröffentlichung von React Hooks habe ich viele Beiträge gesehen, in denen Klassenkomponenten mit Funktionskomponenten verglichen wurden. Funktionale Komponenten sind in React nichts Neues. Vor Version 16.8.0 war es jedoch nicht möglich, eine statusbehaftete Komponente mit Zugriff auf Lifecycle-Hooks nur mit einer Funktion zu erstellen. Oder war es?

Nennen Sie mich einen Pedanten (viele Leute schon!), Aber wenn wir über Klassenkomponenten sprechen, sprechen wir technisch über Komponenten, die durch Funktionen erstellt wurden. In diesem Beitrag möchte ich React verwenden, um zu demonstrieren, was tatsächlich passiert, wenn wir eine Klasse in JavaScript schreiben.

Klassen gegen Funktionen

Zunächst möchte ich ganz kurz zeigen, wie sich die sogenannten Funktions- und Klassenkomponenten zueinander verhalten. Hier ist eine einfache Komponente, die als Klasse geschrieben wurde:

class Hello extends React.Component { render() { return 

Hello!

} }

Und hier steht es als Funktion geschrieben:

function Hello() { return 

Hello!

}

Beachten Sie, dass die Funktionskomponente nur eine Rendermethode ist. Aus diesem Grund waren diese Komponenten niemals in der Lage, ihren eigenen Zustand beizubehalten oder an Punkten während ihres Lebenszyklus irgendwelche Nebenwirkungen auszuführen. Seit React 16.8.0 ist es dank Hooks möglich, zustandsbehaftete Funktionskomponenten zu erstellen, was bedeutet, dass wir eine Komponente wie folgt drehen können:

class Hello extends React.Component { state = { sayHello: false } componentDidMount = () => { fetch('greet') .then(response => response.json()) .then(data => this.setState({ sayHello: data.sayHello }); } render = () => { const { sayHello } = this.state; const { name } = this.props; return sayHello ? 

{`Hello ${name}!`}

: null; } }

In eine Funktionskomponente wie diese:

function Hello({ name }) { const [sayHello, setSayHello] = useState(false); useEffect(() => { fetch('greet') .then(response => response.json()) .then(data => setSayHello(data.sayHello)); }, []); return sayHello ? 

{`Hello ${name}!`}

: null; }

Der Zweck dieses Artikels ist nicht, zu argumentieren, dass einer besser ist als der andere, da es bereits Hunderte von Beiträgen zu diesem Thema gibt! Der Grund für die Darstellung der beiden oben genannten Komponenten ist, dass wir uns darüber im Klaren sind, was React tatsächlich mit ihnen macht.

Im Fall der Klassenkomponente erstellt React eine Instanz der Klasse mit dem newSchlüsselwort:

const instance = new Component(props); 

Diese Instanz ist ein Objekt. Wenn wir sagen, dass eine Komponente eine Klasse ist, meinen wir tatsächlich, dass es sich um ein Objekt handelt. Diese neue Objektkomponente kann einen eigenen Status und eigene Methoden haben. Einige davon können Lebenszyklusmethoden (Rendering, ComponentDidMount usw.) sein, die React an den entsprechenden Punkten während der Lebensdauer der App aufruft.

Bei einer Funktionskomponente ruft React sie einfach wie eine normale Funktion auf (da es sich um eine normale Funktion handelt!) Und gibt entweder HTML oder mehrere React-Komponenten zurück.

Methoden, mit denen der Komponentenstatus behandelt und Effekte an Punkten während des Lebenszyklus der Komponente ausgelöst werden sollen, müssen jetzt importiert werden, wenn sie benötigt werden. Diese funktionieren vollständig in der Reihenfolge, in der sie von jeder Komponente aufgerufen werden, die sie verwendet, da sie nicht wissen, welche Komponente sie aufgerufen hat. Aus diesem Grund können Sie Hooks nur auf der obersten Ebene der Komponente aufrufen und sie können nicht bedingt aufgerufen werden.

Die Konstruktorfunktion

JavaScript hat keine Klassen. Ich weiß, es sieht so aus, als hätte es Klassen, wir haben gerade zwei geschrieben! Unter der Haube ist JavaScript jedoch keine klassenbasierte Sprache, sondern ein Prototyp. Klassen wurden mit der ECMAScript 2015-Spezifikation (auch als ES6 bezeichnet) hinzugefügt und sind nur eine sauberere Syntax für vorhandene Funktionen.

Versuchen wir, eine React-Klassenkomponente ohne Verwendung der Klassensyntax neu zu schreiben. Hier ist die Komponente, die wir neu erstellen werden:

class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 } this.handleClick = this.handleClick.bind(this); } handleClick() { const { count } = this.state; this.setState({ count: count + 1 }); } render() { const { count } = this.state; return (  +1 

{count}

); } }

Dies rendert eine Schaltfläche, die beim Klicken einen Zähler erhöht. Es ist ein Klassiker! Das erste, was wir erstellen müssen, ist die Konstruktorfunktion. Diese führt dieselben Aktionen aus, die die constructorMethode in unserer Klasse ausführt, abgesehen vom Aufruf von, superda dies nur eine Klasse ist.

function Counter(props) { this.state = { count: 0 } this.handleClick = this.handleClick.bind(this); } 

Dies ist die Funktion, die React mit dem newSchlüsselwort aufruft. Wenn eine Funktion mit aufgerufen wird new, wird sie als Konstruktorfunktion behandelt. Ein neues Objekt wird erstellt, die thisVariable wird darauf gezeigt und die Funktion wird ausgeführt, wobei das neue Objekt überall dort verwendet wird, wo thises erwähnt wird.

Als nächstes müssen wir ein Zuhause für die renderund handleClickMethoden finden und dafür müssen wir über die Prototypenkette sprechen.

Die Prototypkette

JavaScript ermöglicht die Vererbung von Eigenschaften und Methoden zwischen Objekten über die sogenannte Prototypenkette.

Nun, ich sage Vererbung, aber ich meine eigentlich Delegation. Im Gegensatz zu anderen Sprachen mit Klassen, in denen Eigenschaften von einer Klasse in ihre Instanzen kopiert werden, verfügen JavaScript-Objekte über einen internen Prototyp-Link, der auf ein anderes Objekt verweist. Wenn Sie eine Methode aufrufen oder versuchen, auf eine Eigenschaft eines Objekts zuzugreifen, sucht JavaScript zuerst nach der Eigenschaft des Objekts. Wenn es dort nicht gefunden werden kann, überprüft es den Prototyp des Objekts (den Link zum anderen Objekt). Wenn es immer noch nicht gefunden werden kann, überprüft es den Prototyp des Prototyps usw. in der Kette, bis es entweder gefunden wird oder keine Prototypen mehr zur Überprüfung vorhanden sind.

Im Allgemeinen stehen alle Objekte in JavaScript Objectam Anfang ihrer Prototypenkette. Auf diese Weise haben Sie Zugriff auf Methoden wie toStringund hasOwnPropertyfür alle Objekte. Die Kette endet, wenn ein Objekt mit nullals Prototyp erreicht wird, dies ist normalerweise bei Object.

Versuchen wir, die Dinge anhand eines Beispiels klarer zu machen.

const parentObject = { name: 'parent' }; const childObject = Object.create(parentObject, { name: { value: 'child' } }); console.log(childObject); 

Zuerst schaffen wir parentObject. Da wir die Objektliteral-Syntax verwendet haben, wird dieses Objekt verknüpft Object. Als nächstes Object.createerstellen wir ein neues Objekt parentObjectals Prototyp.

Wenn wir jetzt console.logunsere drucken childObject, sollten wir sehen:

console output of childObject

The object has two properties, there is the name property which we just set and the __proto___ property. __proto__ isn't an actual property like name, it is an accessor property to the internal prototype of the object. We can expand these to see our prototype chain:

expanded output of childObject

The first __proto___ contains the contents of parentObject which has its own __proto___ containing the contents of Object. These are all of the properties and methods that are available to childObject.

It can be quite confusing that the prototypes are found on a property called __proto__! It's important to realise that __proto__ is only a reference to the linked object. If you use Object.create like we have above, the linked object can be anything you choose, if you use the new keyword to call a constructor function then this linking happens automatically to the constructor function's prototype property.

Ok, back to our component. Since React calls our function with the new keyword, we now know that to make the methods available in our component's prototype chain we just need to add them to the prototype property of the constructor function, like this:

Counter.prototype.render = function() { const { count } = this.state; return (  +1 

{count}

); }, Counter.prototype.handleClick = function () { const { count } = this.state; this.setState({ count: count + 1 }); }

Static Methods

This seems like a good time to mention static methods. Sometimes you might want to create a function which performs some action that pertains to the instances you are creating - but it doesn't really make sense for the function to be available on each object's this. When used with classes they are called Static Methods. I'm not sure if they have a name when not used with classes!

We haven't used any static methods in our example, but React does have a few static lifecycle methods and we did use one earlier with Object.create. It's easy to declare a static method on a class, you just need to prefix the method with the static keyword:

class Example { static staticMethod() { console.log('this is a static method'); } } 

And it's equally easy to add one to a constructor function:

function Example() {} Example.staticMethod = function() { console.log('this is a static method'); } 

In both cases you call the function like this:

Example.staticMethod() 

Extending React.Component

Our component is almost ready, there are just two problems left to fix. The first problem is that React needs to be able to work out whether our function is a constructor function or just a regular function. This is because it needs to know whether to call it with the new keyword or not.

Dan Abramov wrote a great blog post about this, but to cut a long story short, React looks for a property on the component called isReactComponent. We could get around this by adding isReactComponent: {} to Counter.prototype (I know, you would expect it to be a boolean but isReactComponent's value is an empty object. You'll have to read his article if you want to know why!) but that would only be cheating the system and it wouldn't solve problem number two.

In the handleClick method we make a call to this.setState. This method is not on our component, it is "inherited" from React.Component along with isReactComponent. If you remember the prototype chain section from earlier, we want our component instance to first inherit the methods on Counter.prototype and then the methods from React.Component. This means that we want to link the properties on React.Component.prototype to Counter.prototype.__proto__.

Fortunately there's a method on Object which can help us with this:

Object.setPrototypeOf(Counter.prototype, React.Component.prototype); 

It Works!

That's everything we need to do to get this component working with React without using the class syntax. Here's the code for the component in one place if you would like to copy it and try it out for yourself:

function Counter(props) { this.state = { count: 0 }; this.handleClick = this.handleClick.bind(this); } Counter.prototype.render = function() { const { count } = this.state; return (  +1 

{count}

); } Counter.prototype.handleClick = function() { const { count } = this.state; this.setState({ count: count + 1 }); } Object.setPrototypeOf(Counter.prototype, React.Component.prototype);

As you can see, it's not as nice to look at as before. In addtion to making JavaScript more accessible to developers who are used to working with traditional class-based languages, the class syntax also makes the code a lot more readable.

I'm not suggesting that you should start writing your React components in this way (in fact, I would actively discourage it!). I only thought it would be an interesting exercise which would provide some insight into how JavaScript inheritence works.

Although you don't need to understand this stuff to write React components, it certainly can't hurt. I expect there will be occassions when you are fixing a tricky bug where understanding how prototypal inheritence works will make all the difference.

I hope you have found this article interesting and/or enjoyable. You can find more posts that I have written on my blog at hellocode.dev. Thank you.