Як використовувати функції стрілок (поля публічного класу) як методи класу?


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
Mars Robertson

Рішення 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), який, здається, не працює на мене, babel кидає несподівану стрілку маркера на першому =вhandleInputChange =
Ben

40
Ви повинні надати пояснення, наприклад, що це експериментальна особливість для пропозиції ES7.
Фелікс Клінг

1
Поточний проект специфікації був змінений у вересні, тому не слід використовувати його для автооб’єднання, як пропонує Babel.
chico

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


Ваше рішення є приємним, однак воно не охоплює всіх методів життєвого циклу, якщо ви не заявите їх у другому аргументі. Наприклад: 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);
  }
}

Також не рекомендується зв'язувати функцію безпосередньо у візуалізації. Він повинен бути завжди в конструкторі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.