Comment utiliser les fonctions fléchées (champs de classe publique) comme méthodes de classe?

183

Je suis nouveau dans l'utilisation des classes ES6 avec React, auparavant j'avais lié mes méthodes à l'objet actuel (voir dans le premier exemple), mais ES6 me permet-il de lier de manière permanente une fonction de classe à une instance de classe avec des flèches? (Utile lors du passage en tant que fonction de rappel.) J'obtiens des erreurs lorsque j'essaye de les utiliser comme vous le pouvez avec 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);
  }

Ainsi, si je devais passer SomeClass.handleInputChangeà, par exemple setTimeout, il serait limité à l'instance de classe, et non à l' windowobjet.

Ben
la source
1
Je serais intéressé de connaître la même réponse pour TypeScript
Mars Robertson
La solution de TypeScript est la même que la proposition ES7 (voir réponse acceptée ). Ceci est pris en charge nativement par TypeScript.
Philip Bulley
3
Pour tous ceux qui finissent ici, une lecture intéressante sur le sujet "les fonctions fléchées dans les propriétés de classe pourraient ne pas être aussi bonnes que nous le pensons"
Wilt
1
Vous devez éviter d'utiliser les fonctions fléchées dans la classe car elles ne feront pas partie du prototype et ne seront donc pas partagées par toutes les instances. Cela revient à donner la même copie de fonction à chaque instance.
Sourabh Ranka

Réponses:

209

Votre syntaxe est légèrement décalée, il manque juste un signe égal après le nom de la propriété.

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

Ceci est une fonctionnalité expérimentale. Vous devrez activer les fonctionnalités expérimentales de Babel pour que cela soit compilé. Voici une démo avec expérimental activé.

Pour utiliser les fonctionnalités expérimentales de babel, vous pouvez installer le plugin approprié à partir d' ici . Pour cette fonctionnalité spécifique, vous avez besoin du transform-class-propertiesplugin :

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

Vous pouvez en savoir plus sur la proposition de champs de classe et de propriétés statiques ici


Gars
la source
4
(Bien que je sache que cela fonctionne en dehors d'une classe ES6) qui ne semble pas fonctionner pour moi, babel lance une flèche symbolique inattendue au premier =àhandleInputChange =
Ben
40
Vous devez fournir quelques explications, par exemple qu'il s'agit d'une fonctionnalité expérimentale pour une proposition ES7.
Felix Kling du
1
Le projet de spécification actuel a été modifié en septembre, vous ne devriez donc pas l'utiliser pour la liaison automatique comme le propose Babel.
chico
1
Pour Babel 6.3.13, vous avez besoin des préréglages 'es2015' et 'stage-1' activés pour compiler ceci
Andrew
12
Cela fonctionne, mais la méthode est ajoutée à l'instance dans le constructeur au lieu d'être ajoutée au prototype et c'est une grande différence.
lib3d
61

Non, si vous souhaitez créer des méthodes liées et spécifiques à une instance, vous devrez le faire dans le constructeur. Cependant, vous pouvez utiliser les fonctions fléchées pour cela, au lieu d'utiliser .bindsur une méthode prototype:

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

Il existe une proposition qui pourrait vous permettre d'omettre constructor()et de placer directement l'affectation dans la portée de la classe avec la même fonctionnalité, mais je ne recommanderais pas de l'utiliser car elle est hautement expérimentale.

Sinon, vous pouvez toujours utiliser .bind, qui vous permet de déclarer la méthode sur le prototype, puis de la lier à l'instance dans le constructeur. Cette approche a une plus grande flexibilité car elle permet de modifier la méthode de l'extérieur de votre classe.

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

Je sais que cette question a reçu une réponse suffisante, mais j'ai juste une petite contribution à apporter (pour ceux qui ne veulent pas utiliser la fonction expérimentale). En raison du problème de devoir lier plusieurs liaisons de fonction dans le constructeur et de le rendre désordonné, j'ai proposé une méthode utilitaire qui, une fois liée et appelée dans le constructeur, effectue automatiquement toutes les liaisons de méthode nécessaires pour vous.

Supposons que j'ai cette classe avec le constructeur:

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

Ça a l'air désordonné, n'est-ce pas? Maintenant, j'ai créé cette méthode utilitaire

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

Tout ce que j'ai à faire maintenant est d'importer cet utilitaire et d'ajouter un appel à mon constructeur, et je n'ai plus besoin de lier chaque nouvelle méthode dans le constructeur. Le nouveau constructeur semble maintenant propre, comme ceci:

//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
la source
Votre solution est agréable, mais elle ne couvre pas toutes les méthodes du cycle de vie à moins que vous ne les déclariez dans le deuxième argument. Par exemple: shouldComponentUpdateetgetSnapshotBeforeUpdate
WebDeg Brian
Votre idée est similaire à la liaison automatique, qui a une dégradation notable des performances. Il vous suffit de lier les fonctions que vous transmettez. Voir medium.com/@charpeni/…
Michael Freidgeim
3

Vous utilisez la fonction de flèche et la liez également dans le constructeur. Vous n'avez donc pas besoin de faire de liaison lorsque vous utilisez les fonctions fléchées

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

OU vous devez lier une fonction uniquement dans le constructeur lorsque vous utilisez une fonction normale comme ci-dessous

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

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

Il est également déconseillé de lier une fonction directement dans le rendu. Il devrait toujours être dans le constructeur

Hemadri Dasari
la source