Αυτός είναι ο λόγος για τον οποίο πρέπει να δεσμεύσουμε τους χειριστές συμβάντων στα Class Components in React

Ενώ εργάζεστε στο React, πρέπει να έχετε συναντήσει ελεγχόμενα στοιχεία και χειριστές συμβάντων. Πρέπει να συνδέσουμε αυτές τις μεθόδους με την παρουσία στοιχείων χρησιμοποιώντας .bind()τον κατασκευαστή του προσαρμοσμένου στοιχείου μας.

class Foo extends React.Component{ constructor( props ){ super( props ); this.handleClick = this.handleClick.bind(this); } handleClick(event){ // your event handling logic } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Σε αυτό το άρθρο, θα μάθουμε γιατί πρέπει να το κάνουμε αυτό.

Θα συνιστούσα να διαβάσετε .bind()εδώ αν δεν γνωρίζετε ήδη τι κάνει.

Κατηγορήστε το JavaScript, όχι το React

Λοιπόν, η ευθύνη ακούγεται λίγο σκληρή Αυτό δεν είναι κάτι που πρέπει να κάνουμε λόγω του τρόπου λειτουργίας του React ή λόγω του JSX. Αυτό οφείλεται στον τρόπο με τον οποίο λειτουργεί η thisδέσμευση σε JavaScript.

Ας δούμε τι θα συμβεί εάν δεν συνδέσουμε τη μέθοδο χειρισμού συμβάντων με την παρουσία της συνιστώσας:

class Foo extends React.Component{ constructor( props ){ super( props ); } handleClick(event){ console.log(this); // 'this' is undefined } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Εάν εκτελέσετε αυτόν τον κωδικό, κάντε κλικ στο κουμπί "Click Me" και ελέγξτε την κονσόλα σας. Θα δείτε να undefinedεκτυπώνονται στην κονσόλα ως την αξία thisαπό τη μέθοδο χειρισμού συμβάντων. Η handleClick()μέθοδος φαίνεται να έχει χάσει το περιβάλλον (την παρουσία στοιχείου) ή την thisαξία της.

Πώς λειτουργεί αυτό το δεσμευτικό σε JavaScript

Όπως ανέφερα, αυτό συμβαίνει εξαιτίας του τρόπου thisλειτουργίας της δέσμευσης στο JavaScript. Δεν θα αναφερθώ σε πολλές λεπτομέρειες σε αυτήν την ανάρτηση, αλλά εδώ είναι ένας εξαιρετικός πόρος για να καταλάβω πώς λειτουργεί η thisδεσμευτική στο JavaScript.

Όμως, σχετικά με τη συζήτησή μας εδώ, η αξία της thisσυνάρτησης εξαρτάται από τον τρόπο επίκλησης αυτής της συνάρτησης.

Προεπιλεγμένη δέσμευση

function display(){ console.log(this); // 'this' will point to the global object } display(); 

Αυτή είναι μια απλή κλήση λειτουργίας. Η αξία του thisμέσα στο display()μεθόδου σε αυτήν την περίπτωση είναι το παράθυρο - ή η καθολική - αντικείμενο σε μη-αυστηρή λειτουργία. Σε αυστηρή λειτουργία, η thisτιμή είναι undefined.

Σιωπηρή δεσμευτική

var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' points to obj } }; obj.display(); // Saurabh 

Όταν καλούμε μια συνάρτηση με αυτόν τον τρόπο - πριν από ένα αντικείμενο περιβάλλοντος - η thisτιμή στο εσωτερικό display()ορίζεται σε obj.

Αλλά όταν αντιστοιχίζουμε αυτήν τη συνάρτηση αναφοράς σε κάποια άλλη μεταβλητή και επικαλούμε τη συνάρτηση χρησιμοποιώντας αυτήν τη νέα αναφορά συνάρτησης, λαμβάνουμε μια διαφορετική τιμή thisεσωτερικά display().

var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global

Στο παραπάνω παράδειγμα, όταν καλούμε outerDisplay(), δεν καθορίζουμε ένα αντικείμενο περιβάλλοντος. Είναι μια απλή κλήση λειτουργίας χωρίς αντικείμενο ιδιοκτήτη. Σε αυτήν την περίπτωση, η τιμή του thisεσωτερικού display()επιστρέφει στην προεπιλεγμένη δέσμευση . Δείχνει το καθολικό αντικείμενο ή undefinedεάν η συνάρτηση που χρησιμοποιείται χρησιμοποιεί αυστηρή λειτουργία.

Αυτό ισχύει ιδιαίτερα όταν μεταβιβάζετε λειτουργίες όπως επιστροφές κλήσεων σε άλλη προσαρμοσμένη λειτουργία, λειτουργία βιβλιοθήκης τρίτου μέρους ή ενσωματωμένη λειτουργία JavaScript όπως setTimeout.

Εξετάστε τον setTimeoutεικονικό ορισμό όπως φαίνεται παρακάτω και, στη συνέχεια, επικαλέστε τον.

// A dummy implementation of setTimeout function setTimeout(callback, delay){ //wait for 'delay' milliseconds callback(); } setTimeout( obj.display, 1000 );

Μπορούμε να καταλάβουμε ότι όταν καλούμε setTimeout, η JavaScript αντιστοιχεί εσωτερικά obj.displayστο επιχείρημά της callback.

callback = obj.display;

Αυτή η λειτουργία ανάθεσης, όπως έχουμε ξαναδεί, αναγκάζει τη display()λειτουργία να χάσει το περιβάλλον της. Όταν αυτή η επανάκληση επικαλεσθεί τελικά μέσα setTimeout, η thisτιμή μέσα display()επιστρέφει στην προεπιλεγμένη δέσμευση .

var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global

Άμεση σκληρή δέσμευση

Για να αποφευχθεί αυτό, μπορούμε να δεσμεύσουμε ρητά την thisτιμή σε μια συνάρτηση χρησιμοποιώντας τη bind()μέθοδο.

var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh

Τώρα, όταν καλούμε outerDisplay(), η αξία των thisπόντων προς τα objμέσα display().

Ακόμα κι αν περάσουμε obj.displayως επιστροφή κλήσης, η thisτιμή μέσα display()θα δείξει σωστά obj.

Αναδημιουργία του σεναρίου χρησιμοποιώντας μόνο JavaScript

Στην αρχή αυτού του άρθρου, το είδαμε στο στοιχείο React που ονομάζεται Foo. Εάν δεν δεσμεύσαμε το πρόγραμμα χειρισμού συμβάντων this, η τιμή του εντός του προγράμματος χειρισμού συμβάντων ορίστηκε ως undefined.

Όπως ανέφερα και εξήγησα, αυτό οφείλεται στον τρόπο που η thisδέσμευση λειτουργεί στο JavaScript και δεν σχετίζεται με τον τρόπο λειτουργίας του React. Ας καταργήσουμε λοιπόν τον κώδικα που αφορά το React και δημιουργήστε ένα παρόμοιο καθαρό παράδειγμα JavaScript για την προσομοίωση αυτής της συμπεριφοράς.

class Foo { constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh // The assignment operation below simulates loss of context // similar to passing the handler as a callback in the actual // React Component var display = foo.display; display(); // TypeError: this is undefined

Δεν προσομοιώνουμε πραγματικά γεγονότα και χειριστές, αλλά αντ 'αυτού χρησιμοποιούμε συνώνυμο κώδικα. Όπως παρατηρήσαμε στο παράδειγμα του React Component, η thisτιμή ήταν undefinedκαθώς το περιβάλλον χάθηκε αφού περάσει ο χειριστής ως επιστροφή κλήσης - συνώνυμο με μια λειτουργία ανάθεσης. Αυτό παρατηρούμε και εδώ σε αυτό το απόσπασμα JavaScript που δεν αντιδρά.

"Περίμενε ένα λεπτό! Δεν θα έπρεπε η thisτιμή να δείχνει στο καθολικό αντικείμενο, καθώς το εκτελούμε σε μη αυστηρή λειτουργία σύμφωνα με τους κανόνες της προεπιλεγμένης δέσμευσης; " μπορεί να ρωτήσετε.

Όχι. Αυτός είναι ο λόγος:

Τα σώματα των δηλώσεων τάξης και των εκφράσεων τάξης εκτελούνται σε αυστηρό τρόπο, δηλαδή οι κατασκευαστικές, στατικές και πρωτότυπες μέθοδοι. Οι λειτουργίες λήψης και ρύθμισης εκτελούνται σε αυστηρή λειτουργία.

Μπορείτε να διαβάσετε ολόκληρο το άρθρο εδώ.

Έτσι, για να αποτρέψουμε το σφάλμα, πρέπει να δεσμεύσουμε την thisτιμή ως εξής:

class Foo { constructor(name){ this.name = name this.display = this.display.bind(this); } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

Δεν χρειάζεται να το κάνουμε αυτό στον κατασκευαστή και μπορούμε να το κάνουμε και κάπου αλλού. Σκεφτείτε το:

class Foo { constructor(name){ this.name = name; } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display = foo.display.bind(foo); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

But the constructor is the most optimal and efficient place to code our event handler bind statements, considering that this is where all the initialization takes place.

Why don’t we need to bind ‘this’ for Arrow functions?

We have two more ways we can define event handlers inside a React component.

  • Public Class Fields Syntax(Experimental)
class Foo extends React.Component{ handleClick = () => { console.log(this); } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );
  • Arrow function in the callback
class Foo extends React.Component{ handleClick(event){ console.log(this); } render(){ return (  this.handleClick(e)}> Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

Both of these use the arrow functions introduced in ES6. When using these alternatives, our event handler is already automatically bound to the component instance, and we do not need to bind it in the constructor.

The reason is that in the case of arrow functions, this is bound lexically. This means that it uses the context of the enclosing function — or global — scope as its this value.

In the case of the public class fields syntax example, the arrow function is enclosed inside the Foo class — or constructor function — so the context is the component instance, which is what we want.

In the case of the arrow function as callback example, the arrow function is enclosed inside the render() method, which is invoked by React in the context of the component instance. This is why the arrow function will also capture this same context, and the this value inside it will properly point to the component instance.

For more details regarding lexical this binding, check out this excellent resource.

To make a long story short

In Class Components in React, when we pass the event handler function reference as a callback like this

Click Me

η μέθοδος χειριστή συμβάντων χάνει το σιωπηρά δεσμευμένο πλαίσιο. Όταν συμβεί το συμβάν και καλείται ο χειριστής, η thisτιμή επιστρέφει στην προεπιλεγμένη δέσμευση και ορίζεται σε undefined, καθώς οι δηλώσεις τάξης και οι πρωτότυπες μέθοδοι εκτελούνται σε αυστηρή λειτουργία.

Όταν συνδέουμε το thisχειριστή συμβάντων με την παρουσία συνιστωσών στον κατασκευαστή, μπορούμε να το μεταφέρουμε ως επιστροφή κλήσης χωρίς να ανησυχούμε ότι θα χάσει το περιβάλλον του.

Οι συναρτήσεις βέλους εξαιρούνται από αυτήν τη συμπεριφορά επειδή χρησιμοποιούν λεξική thisδέσμευση που τις συνδέει αυτόματα με το πεδίο στο οποίο ορίζονται.