Come utilizzare le funzioni freccia (campi di classe pubblica) come metodi di classe?


181

Sono nuovo nell'uso delle classi ES6 con React, in precedenza ho associato i miei metodi all'oggetto corrente (mostra nel primo esempio), ma ES6 mi consente di associare permanentemente una funzione di classe a un'istanza di classe con le frecce? (Utile quando si passa come funzione di callback.) Ottengo errori quando provo ad usarli come potete con CoffeeScript:

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);
  }

In modo che se dovessi passare SomeClass.handleInputChange, ad esempio setTimeout, sarebbe mirato all'istanza della classe e non windowall'oggetto.


1
Sarei interessato a conoscere la stessa risposta per TypeScript
Mars Robertson il

La soluzione di TypeScript è la stessa della proposta ES7 (vedi risposta accettata ). Questo è supportato nativamente da TypeScript.
Philip Bulley,

2
Per tutti coloro che finiscono qui una lettura interessante sull'argomento "Le funzioni delle frecce nelle proprietà delle classi potrebbero non essere eccezionali come pensiamo"
Wilt

1
Dovresti evitare di usare le funzioni freccia in classe poiché non faranno parte del prototipo e quindi non saranno condivise da ogni istanza. È come dare la stessa copia della funzione a ogni istanza.
Sourabh Ranka,

Risposte:


205

La sintassi è leggermente disattivata, manca solo un segno di uguale dopo il nome della proprietà.

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

Questa è una caratteristica sperimentale. Dovrai abilitare le funzionalità sperimentali in Babel per compilare questo. Ecco una demo con sperimentale abilitato.

Per utilizzare le funzionalità sperimentali in babel è possibile installare il plugin pertinente da qui . Per questa funzione specifica, è necessario il transform-class-propertiesplug-in :

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

Puoi leggere ulteriori informazioni sulla proposta per i campi di classe e le proprietà statiche qui



4
(Anche se so che funziona al di fuori di una classe ES6) che non sembra funzionare per me, Babel lancia una freccia di token inaspettata al primo =ahandleInputChange =
Ben

40
Dovresti fornire alcune spiegazioni, ad esempio che questa è una funzione sperimentale per una proposta ES7.
Felix Kling, l'

1
l'attuale bozza delle specifiche è stata modificata a settembre, quindi non dovresti usarla per l'autobind come propone Babel.
Chico,

1
Per Babel 6.3.13 hai bisogno dei preset 'es2015' e 'stage-1' attivati ​​per compilare questo
Andrew

12
Funziona, ma il metodo viene aggiunto all'istanza nel costruttore anziché essere aggiunto al prototipo ed è una grande differenza.
lib3d

61

No, se si desidera creare metodi specifici associati all'istanza, è necessario farlo nel costruttore. Tuttavia, puoi usare le funzioni freccia per quello, invece di usare .bindun metodo prototipo:

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

Esiste una proposta che potrebbe consentire di omettere constructor()e inserire direttamente il compito nell'ambito della classe con la stessa funzionalità, ma non consiglierei di usarlo poiché è altamente sperimentale.

In alternativa, è sempre possibile utilizzare .bind, che consente di dichiarare il metodo sul prototipo e quindi associarlo all'istanza nel costruttore. Questo approccio ha una maggiore flessibilità in quanto consente di modificare il metodo dall'esterno della classe.

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

4

So che a questa domanda è stata data una risposta sufficiente, ma ho solo un piccolo contributo da dare (per coloro che non vogliono usare la funzione sperimentale). A causa del problema di dover associare più vincoli di funzione nel costruttore e renderlo disordinato, ho trovato un metodo di utilità che una volta associato e chiamato nel costruttore, esegue automaticamente tutti i collegamenti di metodo necessari per te.

Supponiamo di avere questa classe con il costruttore:

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

Sembra disordinato, vero? Ora ho creato questo metodo di utilità

//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);
         });
}

Tutto quello che devo fare ora è importare quell'utilità e aggiungere una chiamata al mio costruttore, e non ho più bisogno di associare ogni nuovo metodo nel costruttore. Il nuovo costruttore ora sembra pulito, in questo modo:

//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);
    }
    // ...
}


La tua soluzione è buona, tuttavia non copre tutti i metodi del ciclo di vita se non li dichiari nel secondo argomento. Ad esempio: shouldComponentUpdateegetSnapshotBeforeUpdate
WebDeg Brian

La tua idea è simile all'autobinding, che presenta un notevole degrado delle prestazioni. Hai solo bisogno di associare le funzioni che passi in giro. Vedi medium.com/@charpeni/…
Michael Freidgeim,

3

Stai usando la funzione freccia e anche la rileghi nel costruttore. Quindi non è necessario eseguire l'associazione quando si utilizzano le funzioni freccia

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

O è necessario associare una funzione solo nel costruttore quando si utilizza la funzione normale come di seguito

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

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

Si consiglia inoltre di non associare una funzione direttamente nel rendering. Dovrebbe sempre essere nel costruttore

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.