Bagaimana cara menggunakan fungsi panah (bidang kelas publik) sebagai metode kelas?

181

Saya baru menggunakan kelas ES6 dengan React, sebelumnya saya telah mengikat metode saya ke objek saat ini (tunjukkan dalam contoh pertama), tetapi apakah ES6 memungkinkan saya untuk secara permanen mengikat fungsi kelas ke instance kelas dengan panah dengan kelas? (Berguna saat melewati sebagai fungsi panggilan balik.) Saya mendapatkan kesalahan ketika saya mencoba menggunakannya sesuka Anda dengan 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);
  }

Sehingga jika saya harus meneruskan SomeClass.handleInputChangeke, misalnya setTimeout, itu akan dicakup ke instance kelas, dan bukan windowobjek.

Ben
sumber
1
Saya akan tertarik mengetahui jawaban yang sama untuk TypeScript
Mars Robertson
Solusi TypeScript sama dengan proposal ES7 (lihat jawaban yang diterima ). Ini didukung secara native oleh TypeScript.
Philip Bulley
2
Untuk semua yang berakhir di sini, bacaan menarik tentang topik "fungsi panah di properti kelas mungkin tidak
Wilt
1
Anda harus menghindari menggunakan fungsi panah di kelas karena mereka tidak akan menjadi bagian dari prototipe dan karenanya tidak dibagikan oleh setiap instance. Ini sama dengan memberikan salinan fungsi yang sama untuk setiap instance.
Sourabh Ranka

Jawaban:

205

Sintaks Anda sedikit tidak aktif, hanya kehilangan tanda sama dengan setelah nama properti.

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

Ini adalah fitur eksperimental. Anda perlu mengaktifkan fitur eksperimental di Babel agar dapat dikompilasi. Ini adalah demo dengan percobaan diaktifkan.

Untuk menggunakan fitur eksperimental dalam babel Anda dapat menginstal plugin yang relevan dari sini . Untuk fitur spesifik ini, Anda memerlukan transform-class-propertiesplugin :

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

Anda dapat membaca lebih lanjut tentang proposal untuk Bidang Kelas dan Properti Statis di sini


Orang
sumber
4
(Meskipun saya tahu itu bekerja di luar kelas ES6) yang tampaknya tidak bekerja untuk saya, babel melempar panah token yang tidak terduga pada pertama =dihandleInputChange =
Ben
40
Anda harus memberikan beberapa penjelasan, misalnya bahwa ini adalah fitur eksperimental untuk proposal ES7.
Felix Kling
1
draft spesifikasi saat ini diubah pada bulan September, jadi Anda tidak boleh menggunakannya untuk autobind seperti yang diusulkan Babel.
chico
1
Untuk Babel 6.3.13 Anda memerlukan preset 'es2015' dan 'stage-1' diaktifkan untuk mengkompilasi ini
Andrew
12
Ini berfungsi, tetapi metode ini ditambahkan ke instance dalam konstruktor alih-alih ditambahkan ke prototipe dan perbedaannya sangat besar.
lib3d
61

Tidak, jika Anda ingin membuat terikat, metode khusus contoh Anda harus melakukannya di konstruktor. Namun, Anda dapat menggunakan fungsi panah untuk itu, alih-alih menggunakan .bindmetode prototipe:

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

Ada proposal yang mungkin memungkinkan Anda untuk menghilangkan constructor()dan langsung menempatkan tugas dalam ruang lingkup kelas dengan fungsi yang sama, tetapi saya tidak akan merekomendasikan untuk menggunakannya karena sangat eksperimental.

Atau, Anda selalu dapat menggunakan .bind, yang memungkinkan Anda untuk mendeklarasikan metode pada prototipe dan kemudian mengikatnya ke instance dalam konstruktor. Pendekatan ini memiliki fleksibilitas yang lebih besar karena memungkinkan memodifikasi metode dari luar kelas Anda.

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

Saya tahu pertanyaan ini sudah cukup dijawab, tetapi saya hanya memiliki sedikit kontribusi (bagi mereka yang tidak ingin menggunakan fitur eksperimental). Karena masalah harus mengikat beberapa fungsi mengikat dalam konstruktor dan membuatnya terlihat berantakan, saya datang dengan metode utilitas yang pernah terikat dan memanggil konstruktor, apakah semua metode yang diperlukan mengikat untuk Anda secara otomatis.

Asumsikan saya memiliki kelas ini dengan konstruktor:

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

Terlihat berantakan, bukan? Sekarang saya membuat metode utilitas ini

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

Yang perlu saya lakukan sekarang adalah mengimpor utilitas itu, dan menambahkan panggilan ke konstruktor saya, dan saya tidak perlu mengikat setiap metode baru di konstruktor lagi. Konstruktor baru sekarang terlihat bersih, seperti ini:

//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
sumber
Solusi Anda bagus, namun tidak mencakup semua metode siklus hidup kecuali Anda menyatakannya dalam argumen kedua. Misalnya: shouldComponentUpdatedangetSnapshotBeforeUpdate
WebDeg Brian
Ide Anda mirip dengan autobinding, yang memiliki penurunan kinerja yang nyata. Anda hanya perlu mengikat fungsi yang Anda bagikan. Lihat medium.com/@charpeni/…
Michael Freidgeim
3

Anda menggunakan fungsi panah dan juga mengikatnya di konstruktor. Jadi Anda tidak perlu melakukan pengikatan saat Anda menggunakan fungsi panah

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

ATAU Anda harus mengikat fungsi hanya di konstruktor ketika Anda menggunakan fungsi normal seperti di bawah ini

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

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

Juga tidak mengikat fungsi secara langsung di render. Itu harus selalu dalam konstruktor

Hemadri Dasari
sumber