¿Cómo usar las funciones de flecha (campos de clase pública) como métodos de clase?

181

Soy nuevo en el uso de clases ES6 con React, anteriormente he estado vinculando mis métodos al objeto actual (mostrar en el primer ejemplo), pero ¿ES6 me permite vincular permanentemente una función de clase a una instancia de clase con flechas? (Útil cuando se pasa como una función de devolución de llamada). Recibo errores cuando trato de usarlos como puede 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);
  }

De modo que si tuviera que pasar SomeClass.handleInputChangea, por ejemplo setTimeout, se limitaría a la instancia de clase, y no al windowobjeto.

Ben
fuente
1
Me interesaría saber la misma respuesta para TypeScript
Mars Robertson el
La solución de TypeScript es la misma que la propuesta de ES7 (ver respuesta aceptada ). Esto es compatible de forma nativa con TypeScript.
Philip Bulley
2
Para todos los que terminan aquí, una lectura interesante sobre el tema "las funciones de flecha en las propiedades de clase podrían no ser tan buenas como creemos"
Wilt
1
Debe evitar el uso de funciones de flecha en clase, ya que no serán parte del prototipo y, por lo tanto, no serán compartidas por todas las instancias. Es lo mismo que dar la misma copia de función a cada instancia.
Sourabh Ranka

Respuestas:

205

Su sintaxis está ligeramente apagada, solo falta un signo igual después del nombre de la propiedad.

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

Esta es una característica experimental. Deberá habilitar las funciones experimentales en Babel para que esto se compile. Aquí hay una demostración con experimental habilitado.

Para usar funciones experimentales en babel, puede instalar el complemento correspondiente desde aquí . Para esta característica específica, necesita el transform-class-propertiescomplemento :

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

Puede leer más sobre la propuesta de Campos de clase y Propiedades estáticas aquí


Chico
fuente
44
(Aunque sé que funciona fuera de una clase de ES6) que no parece funcionar para mí, Babel lanza una flecha de token inesperada al principio =a lashandleInputChange =
Ben
40
Debe proporcionar alguna explicación, por ejemplo, que esta es una característica experimental para una propuesta de ES7.
Felix Kling
1
El borrador actual de la especificación se modificó en septiembre, por lo que no debe usarlo para la vinculación automática como propone Babel.
chico
1
Para Babel 6.3.13 necesita los presets 'es2015' y 'stage-1' activados para compilar esto
Andrew
12
Funciona, pero el método se agrega a la instancia en el constructor en lugar de agregarse al prototipo y es una gran diferencia.
lib3d
61

No, si desea crear métodos vinculados específicos de instancia, deberá hacerlo en el constructor. Sin embargo, puede usar funciones de flecha para eso, en lugar de usar .bindun método prototipo:

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

Hay una propuesta que podría permitirle omitir constructor()y colocar directamente la asignación en el alcance de la clase con la misma funcionalidad, pero no recomendaría usarla ya que es altamente experimental.

Alternativamente, siempre puede usar .bind, lo que le permite declarar el método en el prototipo y luego vincularlo a la instancia en el constructor. Este enfoque tiene una mayor flexibilidad ya que permite modificar el método desde el exterior de su clase.

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

Sé que esta pregunta ha sido suficientemente respondida, pero solo tengo que hacer una pequeña contribución (para aquellos que no quieren usar la función experimental). Debido al problema de tener que vincular enlaces de múltiples funciones en el constructor y hacer que se vea desordenado, se me ocurrió un método de utilidad que una vez vinculado y llamado en el constructor, realiza automáticamente todos los enlaces de métodos necesarios para usted.

Supongamos que tengo esta clase con el constructor:

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

Se ve desordenado, ¿no? Ahora creé este método de utilidad

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

Todo lo que tengo que hacer ahora es importar esa utilidad y agregar una llamada a mi constructor, y ya no necesito vincular cada método nuevo en el constructor. El nuevo constructor ahora se ve limpio, así:

//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
fuente
Su solución es buena, sin embargo, no cubre todos los métodos del ciclo de vida a menos que los declare en el segundo argumento. Por ejemplo: shouldComponentUpdateygetSnapshotBeforeUpdate
WebDeg Brian
Su idea es similar a la vinculación automática, que tiene una degradación notable del rendimiento. Solo necesita vincular las funciones que pasa. Ver medium.com/@charpeni/…
Michael Freidgeim
3

Está utilizando la función de flecha y también la vincula en el constructor. Por lo tanto, no necesita vincular cuando usa las funciones de flecha

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

O necesita vincular una función solo en el constructor cuando usa la función normal como se muestra a continuación

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

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

Tampoco se recomienda vincular una función directamente en render. Siempre debe estar en constructor

Hemadri Dasari
fuente