Как использовать функции стрелок (открытые поля классов) в качестве методов класса?

181

Я новичок в использовании классов ES6 с React, ранее я связывал свои методы с текущим объектом (покажите в первом примере), но позволяет ли ES6 навсегда привязать функцию класса к экземпляру класса с помощью стрелок? (Полезно при передаче в качестве функции обратного вызова.) Я получаю ошибки, когда пытаюсь использовать их как можно с 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);
  }

Так что если бы я должен был пройти SomeClass.handleInputChange , например setTimeout, он был бы ограничен экземпляром класса, а не windowобъектом.

Бен
источник
1
Мне было бы интересно узнать тот же ответ для TypeScript
Марс Робертсон
Решение TypeScript такое же, как и предложение ES7 (см. Принятый ответ ). Это изначально поддерживается TypeScript.
Филипп Булли
2
Для всех, кто попал сюда, интересное прочтение на тему «Функции стрелок в свойствах классов могут быть не такими
Уилт
1
Вы должны избегать использования функций стрелок в классе, так как они не будут частью прототипа и поэтому не будут использоваться всеми экземплярами. Это то же самое, что дать одну и ту же копию функции каждому экземпляру.
Сураб Ранка

Ответы:

205

Ваш синтаксис немного отключен, просто отсутствует знак равенства после имени свойства.

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

Это экспериментальная особенность. Вам нужно будет включить экспериментальные функции в Babel, чтобы компилировать это. Вот демо с включенным экспериментом.

Для использования экспериментальных функций в Babel вы можете установить соответствующий плагин отсюда . Для этой конкретной функции вам нужен transform-class-propertiesплагин :

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

Вы можете прочитать больше о предложении для полей класса и статических свойств здесь


парень
источник
4
(Хотя я знаю , что работает за пределами класса ES6) , которые не появляются на работе для меня, столпотворение бросает неожиданный маркер стрелка в первой =вhandleInputChange =
Бен
40
Вы должны предоставить какое-то объяснение, например, что это экспериментальная функция для предложения ES7.
Феликс Клинг
1
черновик текущей спецификации был изменен в сентябре, поэтому вы не должны использовать его для автосвязки, как предлагает Babel.
Чико
1
Для Babel 6.3.13 вам нужны активированные пресеты «es2015» и «stage-1», чтобы скомпилировать это
Эндрю
12
Это работает, но метод добавляется в экземпляр в конструкторе, а не в прототип, и это большая разница.
lib3d
61

Нет, если вы хотите создать связанные, специфичные для экземпляра методы, вам придется сделать это в конструкторе. Однако для этого вы можете использовать функции стрелок вместо .bindметода-прототипа:

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

Существует предложение, которое может позволить вам опуститьconstructor() и непосредственно поместить назначение в область видимости класса с той же функциональностью, но я бы не рекомендовал использовать это, поскольку это очень экспериментально.

В качестве альтернативы вы всегда можете использовать .bind, что позволяет вам объявить метод в прототипе и затем связать его с экземпляром в конструкторе. Этот подход обладает большей гибкостью, поскольку позволяет изменять метод извне вашего класса.

class SomeClass extends React.Component {
  constructor() {
    super();
    this.handleInputChange = this.handleInputChange.bind(this);
    
  }
  handleInputChange(val) {
    console.log('selectionMade: ', val, this);
  }
}
Берги
источник
4

Я знаю, что на этот вопрос был дан достаточный ответ, но у меня есть небольшой вклад (для тех, кто не хочет использовать экспериментальную функцию). Из-за проблемы привязки нескольких привязок функций в конструкторе и придания ему грязного вида, я пришел к вспомогательному методу, который после привязки и вызова в конструкторе автоматически выполняет все необходимые привязки методов.

Предположим, у меня есть этот класс с конструктором:

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

Это выглядит грязно, не так ли? Теперь я создал этот служебный метод

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

Все, что мне теперь нужно, это импортировать эту утилиту и добавить вызов в мой конструктор, и мне больше не нужно связывать каждый новый метод в конструкторе. Новый конструктор теперь выглядит чистым, вот так:

//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
источник
Ваше решение хорошо, но оно не охватывает все методы жизненного цикла, если вы не объявите их во втором аргументе. Например: shouldComponentUpdateиgetSnapshotBeforeUpdate
WebDeg Брайан
Ваша идея похожа на автоматическое связывание, которое имеет заметное снижение производительности. Вам нужно только связать функции, которые вы передаете. См. Medium.com/@charpeni/…
Майкл
3

Вы используете функцию стрелки, а также связываете ее в конструкторе. Так что вам не нужно делать привязку при использовании функций стрелок

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

ИЛИ вам нужно привязать функцию только в конструкторе, когда вы используете обычную функцию, как показано ниже

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

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

Также не рекомендуется связывать функцию непосредственно при рендеринге. Это всегда должно быть в конструкторе

Хемадри Дасари
источник