Como usar as funções de seta (campos de classe pública) como métodos de classe?

181

Eu sou novo no uso de classes ES6 com React; anteriormente, vinculei meus métodos ao objeto atual (mostrado no primeiro exemplo), mas o ES6 me permite vincular permanentemente uma função de classe a uma instância de classe com setas? (Útil ao passar como uma função de retorno de chamada.) Eu recebo erros quando tento usá-los como você pode com o 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);
  }

Portanto, se eu fosse passar SomeClass.handleInputChangepara, por exemplo setTimeout, teria como escopo a instância da classe, e não o windowobjeto.

Ben
fonte
1
Eu estaria interessado em saber a mesma resposta para typescript
Mars Robertson
A solução do TypeScript é igual à proposta do ES7 (consulte a resposta aceita ). Isso é suportado nativamente pelo TypeScript.
Philip Bulley
2
Para todos os que acabam aqui, uma leitura interessante sobre o tópico "funções de seta nas propriedades de classe pode não ser tão boa quanto pensamos"
Wilt
1
Você deve evitar o uso de funções de seta na classe, pois elas não farão parte do protótipo e, portanto, não serão compartilhadas por todas as instâncias. É o mesmo que fornecer a mesma cópia da função para cada instância.
Sourabh Ranka

Respostas:

205

Sua sintaxe está um pouco desativada, faltando apenas um sinal de igual após o nome da propriedade.

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

Este é um recurso experimental. Você precisará habilitar os recursos experimentais no Babel para que isso seja compilado. Aqui está uma demonstração com o experimental ativado.

Para usar recursos experimentais no babel, você pode instalar o plugin relevante a partir daqui . Para esse recurso específico, você precisa do transform-class-propertiesplugin :

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

Você pode ler mais sobre a proposta para campos de classe e propriedades estáticas aqui


Cara
fonte
4
(Embora eu saiba que as obras fora de uma classe ES6) que não aparecem para trabalhar para mim, babel lança uma seta token inesperado na primeira =emhandleInputChange =
Ben
40
Você deve fornecer algumas explicações, por exemplo, que esse é um recurso experimental para uma proposta do ES7.
Felix Kling
1
o rascunho da especificação atual foi alterado em setembro; portanto, você não deve usá-lo para ligação automática, como propõe Babel.
chico
1
Para Babel 6.3.13, você precisa das predefinições 'es2015' e 'stage-1' ativadas para compilar isso
Andrew
12
Funciona, mas o método é adicionado à instância no construtor em vez de ser adicionado ao protótipo e é uma grande diferença.
Lib3d
61

Não, se você deseja criar métodos vinculados, específicos da instância, precisará fazer isso no construtor. No entanto, você pode usar funções de seta para isso, em vez de usar .bindem um método de protótipo:

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

Existe uma proposta que pode permitir que você omita constructor()e coloque diretamente a tarefa no escopo da classe com a mesma funcionalidade, mas eu não recomendaria usá-la, pois é altamente experimental.

Como alternativa, você sempre pode usar .bind, o que permite declarar o método no protótipo e vinculá-lo à instância no construtor. Essa abordagem tem maior flexibilidade, pois permite modificar o método de fora da sua classe.

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

Sei que esta pergunta foi respondida suficientemente, mas só tenho uma pequena contribuição a fazer (para aqueles que não querem usar o recurso experimental). Devido ao problema de ter que vincular várias funções a um construtor e torná-lo bagunçado, criei um método utilitário que, uma vez vinculado e chamado no construtor, faz todas as ligações necessárias para você automaticamente.

Suponha que eu tenho essa classe com o construtor:

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

Parece confuso, não é? Agora eu criei esse método utilitário

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

Tudo o que agora preciso fazer é importar esse utilitário e adicionar uma chamada ao meu construtor, e não preciso vincular cada novo método ao construtor. O novo construtor agora parece limpo, assim:

//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
fonte
Sua solução é boa, mas não abrange todos os métodos de ciclo de vida, a menos que você os declare no segundo argumento. Por exemplo: shouldComponentUpdateegetSnapshotBeforeUpdate
WebDeg Brian
Sua ideia é semelhante à ligação automática, que apresenta uma degradação notável no desempenho. Você só precisa vincular funções que repassa. Veja medium.com/@charpeni/…
Michael Freidgeim
3

Você está usando a função de seta e também a vincula no construtor. Portanto, você não precisa vincular quando usa as funções de seta

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

OU você precisa vincular uma função apenas no construtor quando usar a função normal, como abaixo

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

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

Também não é recomendável vincular uma função diretamente na renderização. Deve sempre estar no construtor

Hemadri Dasari
fonte