Wie verwende ich Pfeilfunktionen (öffentliche Klassenfelder) als Klassenmethoden?

181

Ich bin neu in der Verwendung von ES6-Klassen mit React. Zuvor habe ich meine Methoden an das aktuelle Objekt gebunden (siehe erstes Beispiel). Kann ich mit ES6 eine Klassenfunktion dauerhaft mit Pfeilen an eine Klasseninstanz binden? (Nützlich beim Übergeben als Rückruffunktion.) Ich erhalte Fehler, wenn ich versuche, sie so gut wie möglich mit CoffeeScript zu verwenden:

class SomeClass extends React.Component {

  // Instead of this
  constructor(){
    this.handleInputChange = this.handleInputChange.bind(this)
  }

  // Can I somehow do this? Am i just getting the syntax wrong?
  handleInputChange (val) => {
    console.log('selectionMade: ', val);
  }

Wenn ich SomeClass.handleInputChangezum Beispiel an übergeben würde setTimeout, würde dies auf die Klasseninstanz und nicht auf das windowObjekt beschränkt sein.

Ben
quelle
1
Ich würde gerne die gleiche Antwort für TypeScript
Mars Robertson
Die Lösung von TypeScript entspricht der des ES7-Vorschlags (siehe akzeptierte Antwort ). Dies wird nativ von TypeScript unterstützt.
Philip Bulley
2
Für alle, die hier landen, ist eine interessante Lektüre zum Thema "Pfeilfunktionen in Klasseneigenschaften sind möglicherweise nicht so gut wie wir denken"
Wilt
1
Sie sollten die Verwendung von Pfeilfunktionen in der Klasse vermeiden, da diese nicht Teil des Prototyps sind und daher nicht von jeder Instanz gemeinsam genutzt werden. Es ist dasselbe, als würde man jeder Instanz dieselbe Kopie der Funktion geben.
Sourabh Ranka

Antworten:

205

Ihre Syntax ist leicht falsch und es fehlt nur ein Gleichheitszeichen nach dem Eigenschaftsnamen.

class SomeClass extends React.Component {
  handleInputChange = (val) => {
    console.log('selectionMade: ', val);
  }
}

Dies ist eine experimentelle Funktion. Sie müssen experimentelle Funktionen in Babel aktivieren, damit diese kompiliert werden können. Hier ist eine Demo mit aktiviertem Experiment.

Um experimentelle Funktionen in babel zu nutzen, können Sie hier das entsprechende Plugin installieren . Für diese spezielle Funktion benötigen Sie das transform-class-propertiesPlugin :

{
  "plugins": [
    "transform-class-properties"
  ]
}

Weitere Informationen zum Vorschlag für Klassenfelder und statische Eigenschaften finden Sie hier


Kerl
quelle
4
(Obwohl ich , dass die Arbeiten außerhalb einer ES6 Klasse kennen) , die nicht für mich zur Arbeit erscheint, wirft babel einen unerwarteten Token Pfeil am ersten =beihandleInputChange =
Ben
40
Sie sollten einige Erklärungen abgeben, z. B. dass dies eine experimentelle Funktion für einen ES7-Vorschlag ist.
Felix Kling
1
Der aktuelle Spezifikationsentwurf wurde im September geändert, daher sollten Sie ihn nicht für die automatische Bindung verwenden, wie Babel vorschlägt.
Chico
1
Für Babel 6.3.13 müssen die Voreinstellungen 'es2015' und 'stage-1' aktiviert sein, um dies zu kompilieren
Andrew
12
Es funktioniert, aber die Methode wird der Instanz im Konstruktor hinzugefügt, anstatt dem Prototyp hinzugefügt zu werden, und es ist ein großer Unterschied.
lib3d
61

Nein, wenn Sie gebundene, instanzspezifische Methoden erstellen möchten, müssen Sie dies im Konstruktor tun. Sie können jedoch dafür Pfeilfunktionen verwenden, anstatt .bindeine Prototypmethode zu verwenden:

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = (val) => {
      console.log('selectionMade: ', val, this);
    };
    
  }
}

Es gibt einen Vorschlag, mit dem Sie constructor()die Zuweisung möglicherweise weglassen und die Zuordnung mit derselben Funktionalität direkt in den Klassenbereich einfügen können. Ich würde jedoch nicht empfehlen, diesen zu verwenden, da er sehr experimentell ist.

Alternativ können Sie immer verwenden .bind, um die Methode für den Prototyp zu deklarieren und sie dann an die Instanz im Konstruktor zu binden. Dieser Ansatz bietet eine größere Flexibilität, da Sie die Methode von außerhalb Ihrer Klasse ändern können.

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = this.handleInputChange.bind(this);
    
  }
  handleInputChange(val) {
    console.log('selectionMade: ', val, this);
  }
}
Bergi
quelle
3

Sie verwenden die Pfeilfunktion und binden sie auch im Konstruktor. Sie müssen also keine Bindung durchführen, wenn Sie Pfeilfunktionen verwenden

class SomeClass extends React.Component {
  handleInputChange = (val) => {
    console.log('selectionMade: ', val);
  }
}

ODER Sie müssen eine Funktion nur im Konstruktor binden, wenn Sie die normale Funktion wie unten verwenden

class SomeClass extends React.Component {
   constructor(props){
      super(props);
      this.handleInputChange = this.handleInputChange.bind(this);
   }

  handleInputChange(val){
    console.log('selectionMade: ', val);
  }
}

Auch das direkte Binden einer Funktion in Render wird nicht empfohlen. Es sollte immer im Konstruktor sein

Hemadri Dasari
quelle
3

Ich weiß, dass diese Frage ausreichend beantwortet wurde, aber ich muss nur einen kleinen Beitrag leisten (für diejenigen, die die experimentelle Funktion nicht verwenden möchten). Aufgrund des Problems, mehrere Funktionsbindungen im Konstruktor binden zu müssen und es unordentlich aussehen zu lassen, habe ich eine Dienstprogrammmethode entwickelt, die nach dem Binden und Aufrufen im Konstruktor alle erforderlichen Methodenbindungen automatisch für Sie ausführt.

Angenommen, ich habe diese Klasse mit dem Konstruktor:

//src/components/PetEditor.jsx
import React from 'react';
class PetEditor extends React.Component {
  
   constructor(props){
        super(props);
        this.state = props.currentPet || {tags:[], photoUrls: []};
     
        this.tagInput = null;
        this.htmlNode = null;

        this.removeTag = this.removeTag.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.modifyState = this.modifyState.bind(this);
        this.handleKeyUp = this.handleKeyUp.bind(this);
        this.addTag = this.addTag.bind(this);
        this.removeTag = this.removeTag.bind(this);
        this.savePet = this.savePet.bind(this);
        this.addPhotoInput = this.addPhotoInput.bind(this);
        this.handleSelect = this.handleSelect.bind(this);
        
    }
    // ... actual method declarations omitted
}

Es sieht chaotisch aus, nicht wahr? Jetzt habe ich diese Dienstprogrammmethode erstellt

//src/utils/index.js
/**
 *  NB: to use this method, you need to bind it to the object instance calling it
 */
export function bindMethodsToSelf(objClass, otherMethodsToIgnore=[]){
    const self = this;
    Object.getOwnPropertyNames(objClass.prototype)
        .forEach(method => {
              //skip constructor, render and any overrides of lifecycle methods
              if(method.startsWith('component') 
                 || method==='constructor' 
                 || method==='render') return;
              //any other methods you don't want bound to self
              if(otherMethodsToIgnore.indexOf(method)>-1) return;
              //bind all other methods to class instance
              self[method] = self[method].bind(self);
         });
}

Jetzt muss ich nur noch dieses Dienstprogramm importieren und meinem Konstruktor einen Aufruf hinzufügen, und ich muss nicht mehr jede neue Methode im Konstruktor binden. Der neue Konstruktor sieht jetzt sauber aus:

//src/components/PetEditor.jsx
import React from 'react';
import { bindMethodsToSelf } from '../utils';
class PetEditor extends React.Component {
    constructor(props){
        super(props);
        this.state = props.currentPet || {tags:[], photoUrls: []};
        this.tagInput = null;
        this.htmlNode = null;
        bindMethodsToSelf.bind(this)(PetEditor);
    }
    // ...
}

kennasoft
quelle
Ihre Lösung ist nett, deckt jedoch nicht alle Lebenszyklusmethoden ab, es sei denn, Sie deklarieren sie im zweiten Argument. Zum Beispiel: shouldComponentUpdateundgetSnapshotBeforeUpdate
WebDeg Brian
Ihre Idee ähnelt der Autobindung, bei der die Leistung spürbar abnimmt. Sie müssen nur Funktionen binden, die Sie weitergeben. Siehe medium.com/@charpeni/…
Michael Freidgeim