Jak używać funkcji strzałek (pól klasy publicznej) jako metod klasowych?

181

Jestem nowy w używaniu klas ES6 z React, wcześniej wiązałem swoje metody z bieżącym obiektem (pokaż w pierwszym przykładzie), ale czy ES6 pozwala mi na stałe powiązać funkcję klasy z instancją klasy za pomocą strzałek? (Przydatne przy przekazywaniu jako funkcja oddzwaniania). Występują błędy, gdy próbuję ich użyć w sposób możliwy z 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);
  }

Gdybym więc miał przejść SomeClass.handleInputChangedo, na przykład setTimeout, byłby objęty zakresem instancji klasy, a nie windowobiektu.

Ben
źródło
1
Byłbym zainteresowany poznaniem tej samej odpowiedzi dla TypeScript
Mars Robertson,
Rozwiązanie TypeScript jest takie samo jak propozycja ES7 (patrz zaakceptowana odpowiedź ). Jest to obsługiwane natywnie przez TypeScript.
Philip Bulley,
2
Dla wszystkich, którzy tu znajdą się ciekawa lektura na temat „Funkcje strzałek we właściwościach klasy mogą nie być tak świetne, jak nam się wydaje”
Wilt,
1
Należy unikać używania funkcji strzałek w klasie, ponieważ nie będą one częścią prototypu, a zatem nie będą współdzielone przez każdą instancję. To tak samo jak nadanie tej samej kopii funkcji każdej instancji.
Sourabh Ranka,

Odpowiedzi:

205

Twoja składnia jest nieco wyłączona, po prostu brakuje znaku równości po nazwie właściwości.

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

To jest funkcja eksperymentalna. Musisz włączyć funkcje eksperymentalne w Babel, aby to skompilować. Oto demo z włączoną funkcją eksperymentalną.

Aby użyć eksperymentalnych funkcji w Babel, możesz zainstalować odpowiednią wtyczkę tutaj . Do tej konkretnej funkcji potrzebujesz transform-class-propertieswtyczki :

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

Więcej informacji na temat propozycji pól klas i właściwości statycznych można znaleźć tutaj


Chłopak
źródło
4
(Chociaż wiem, że działa poza klasą ES6), która wydaje się nie działać dla mnie, Babel rzuca nieoczekiwaną strzałkę symboliczną na początku =ohandleInputChange =
Ben
40
Powinieneś podać jakieś wyjaśnienie, np. Że jest to funkcja eksperymentalna dla propozycji ES7.
Felix Kling
1
aktualny projekt specyfikacji został zmieniony we wrześniu, więc nie powinieneś używać go do autobind, jak proponuje Babel.
chico
1
Dla Babel 6.3.13 potrzebne są presety „es2015” i „stage-1” aktywowane, aby to skompilować
Andrew
12
Działa, ale metoda jest dodawana do instancji w konstruktorze zamiast dodawana do prototypu i jest to duża różnica.
lib3d 21.01.16
61

Nie, jeśli chcesz utworzyć powiązane metody specyficzne dla instancji, musisz to zrobić w konstruktorze. Możesz jednak użyć do tego funkcji strzałek zamiast .bindmetody prototypowej:

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

Istnieje propozycja, która może pozwolić ci pominąć constructor()i bezpośrednio umieścić zadanie w zakresie klasy o tej samej funkcjonalności, ale nie zalecałbym używania tego, ponieważ jest to wysoce eksperymentalne.

Alternatywnie można zawsze użyć .bind, co pozwala zadeklarować metodę na prototypie, a następnie powiązać ją z instancją w konstruktorze. To podejście ma większą elastyczność, ponieważ pozwala modyfikować metodę z zewnątrz klasy.

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = this.handleInputChange.bind(this);
    
  }
  handleInputChange(val) {
    console.log('selectionMade: ', val, this);
  }
}
Bergi
źródło
4

Wiem, że odpowiedź na to pytanie jest wystarczająca, ale mam tylko niewielki wkład (dla tych, którzy nie chcą korzystać z funkcji eksperymentalnej). Z powodu problemu z wiązaniem wielu powiązań funkcji w konstruktorze i powodowania bałaganu, wymyśliłem metodę narzędzia, która raz powiązana i wywołana w konstruktorze, automatycznie wykonuje wszystkie niezbędne wiązania metod.

Załóżmy, że mam tę klasę z konstruktorem:

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

Wygląda niechlujnie, prawda? Teraz stworzyłem tę metodę narzędzia

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

Teraz muszę tylko zaimportować to narzędzie i dodać wywołanie do mojego konstruktora, i nie muszę już wiązać każdej nowej metody w konstruktorze. Nowy konstruktor wygląda teraz na czysty:

//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
źródło
Twoje rozwiązanie jest dobre, jednak nie obejmuje wszystkich metod cyklu życia, chyba że zadeklarujesz je w drugim argumencie. Na przykład: shouldComponentUpdateigetSnapshotBeforeUpdate
WebDeg Brian
Twój pomysł jest podobny do autobindingu, który ma zauważalny spadek wydajności. Musisz tylko powiązać przekazywane funkcje. Zobacz medium.com/@charpeni/…
Michael Freidgeim
3

Używasz funkcji strzałki, a także wiążesz ją w konstruktorze. Nie trzeba więc wiązać podczas korzystania z funkcji strzałek

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

LUB musisz powiązać funkcję tylko w konstruktorze, gdy używasz normalnej funkcji, jak poniżej

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

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

Nie jest również zalecane wiązanie funkcji bezpośrednio w renderowaniu. Zawsze powinien być konstruktorem

Hemadri Dasari
źródło