Mengapa alat peraga JSX tidak menggunakan fungsi panah atau mengikat?

103

Saya menjalankan lint dengan aplikasi React saya, dan saya menerima kesalahan ini:

error    JSX props should not use arrow functions        react/jsx-no-bind

Dan di sinilah saya menjalankan fungsi panah (di dalam onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

Apakah ini praktik buruk yang harus dihindari? Dan apa cara terbaik untuk melakukannya?

KadoBOT
sumber

Jawaban:

170

Mengapa Anda tidak boleh menggunakan fungsi panah sebaris di alat peraga JSX

Menggunakan fungsi panah atau pengikatan di JSX adalah praktik buruk yang merusak performa, karena fungsi tersebut dibuat ulang pada setiap render.

  1. Setiap kali fungsi dibuat, fungsi sebelumnya adalah sampah yang dikumpulkan. Merender ulang banyak elemen dapat membuat animasi jank.

  2. Menggunakan fungsi panah sebaris akan menyebabkan PureComponents, dan komponen yang digunakan shallowComparedalam shouldComponentUpdatemetode tetap merender. Karena prop fungsi panah dibuat ulang setiap kali, perbandingan dangkal akan mengidentifikasinya sebagai perubahan pada prop, dan komponen akan merender ulang.

Seperti yang Anda lihat pada 2 contoh berikut - ketika kita menggunakan fungsi panah sebaris, <Button>komponen dirender setiap kali (konsol menampilkan teks 'tombol render').

Contoh 1 - PureComponent tanpa handler inline

Contoh 2 - PureComponent dengan handler inline

Mengikat metode ke thistanpa fungsi panah sebaris

  1. Mengikat metode secara manual di konstruktor:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Mengikat metode menggunakan proposal-class-fields dengan fungsi panah. Karena ini adalah proposal tahap 3, Anda perlu menambahkan preset Tahap 3 atau properti Kelas diubah ke konfigurasi babel Anda.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Komponen Fungsi dengan panggilan balik batin

Saat kita membuat fungsi bagian dalam (penanganan peristiwa misalnya) di dalam komponen fungsi, fungsi tersebut akan dibuat ulang setiap kali komponen dirender. Jika fungsi diteruskan sebagai props (atau melalui konteks) ke komponen anak ( Buttondalam hal ini), anak itu juga akan dirender ulang.

Contoh 1 - Komponen Fungsi dengan callback batin:

Untuk mengatasi masalah ini, kita bisa membungkus callback dengan useCallback()hook , dan mengatur dependensi ke array kosong.

Catatan: yang useStatefungsi yang dihasilkan menerima fungsi updater, yang menyediakan kondisi saat ini. Dengan cara ini, kita tidak perlu menyetel ketergantungan pada status saat ini useCallback.

Contoh 2 - Komponen Fungsi dengan callback dalam yang dibungkus dengan useCallback:

Ori Drori
sumber
3
Bagaimana Anda mencapai ini pada komponen tanpa negara?
lux
4
Komponen stateless (fungsi) tidak memiliki this, jadi tidak ada yang perlu diikat. Biasanya metode ini disediakan oleh komponen pintar pembungkus.
Ori Drori
39
@OriDrori: Bagaimana cara kerjanya saat Anda perlu meneruskan data di callback? onClick={() => { onTodoClick(todo.id) }
adam-beck
4
@ adam-beck - menambahkannya di dalam definisi metode panggilan balik di kelas cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Saya rasa inilah cara menggunakan useCallbackdengan nilai dinamis. stackoverflow.com/questions/55006061/…
Shota Tamura
9

Ini karena fungsi panah tampaknya akan membuat instance baru dari fungsi tersebut pada setiap render jika digunakan dalam properti JSX. Hal ini dapat membuat beban berat pada pengumpul sampah dan juga akan menghalangi browser untuk mengoptimalkan "jalur panas" karena fungsi akan dibuang alih-alih digunakan kembali.

Anda dapat melihat penjelasan lengkapnya dan beberapa info lebih lanjut di https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
sumber
Tidak hanya itu. Membuat instance fungsi baru setiap kali berarti statusnya diubah dan ketika status komponen diubah, akan dirender ulang. Karena salah satu alasan utama menggunakan React adalah hanya membuat elemen yang berubah, menggunakan bindatau fungsi panah di sini adalah menembak diri Anda sendiri di kaki. Ini tidak didokumentasikan dengan baik, terutama dalam kasus bekerja dengan maplarik ping dalam Daftar, dll.
hippietrail
"Membuat instance fungsi baru setiap kali berarti statusnya diubah" apa yang Anda maksud dengan itu? Tidak ada pernyataan sama sekali dalam pertanyaan
masing
4

Untuk menghindari pembuatan fungsi baru dengan argumen yang sama, Anda dapat membuat memo hasil fungsi bind, berikut adalah utilitas sederhana bernama memobinduntuk melakukannya: https://github.com/supnate/memobind

supNate
sumber
4

Menggunakan fungsi sebaris seperti ini tidak masalah. Aturan linting sudah usang.

Aturan ini berasal dari saat fungsi panah tidak umum dan orang menggunakan .bind (this), yang dulu lambat. Masalah kinerja telah diperbaiki di Chrome 49.

Perhatikan bahwa Anda tidak meneruskan fungsi inline sebagai props ke komponen anak.

Ryan Florence, penulis React Router, telah menulis banyak hal tentang ini:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

sbaechler.dll
sumber
Bisakah Anda menunjukkan cara menulis pengujian unit pada komponen dengan fungsi panah sebaris?
krankuba
1
@krankuba Pertanyaan ini bukanlah tentang apa. Anda masih bisa meneruskan fungsi anonim yang tidak ditentukan sebaris tetapi masih belum bisa diuji.
sbaechler
-1

Anda dapat menggunakan fungsi panah menggunakan pustaka react-cached-handler , tidak perlu khawatir tentang kinerja rendering ulang:

Catatan: Secara internal, ini menyimpan fungsi panah Anda dengan kunci yang ditentukan, tidak perlu khawatir tentang rendering ulang!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Fitur lainnya:

  • Penangan bernama
  • Tangani acara dengan fungsi panah
  • Akses ke kunci, argumen kustom, dan peristiwa asli
  • Performa rendering komponen
  • Konteks khusus untuk penangan
Ghominejad
sumber
Pertanyaannya adalah mengapa kita tidak bisa menggunakannya. Bukan cara menggunakannya dengan beberapa peretasan lainnya.
kapil
-1

Mengapa alat peraga JSX tidak menggunakan fungsi panah atau mengikat?

Sebagian besar, karena fungsi inline dapat merusak memoisasi komponen yang dioptimalkan:

Secara tradisional, masalah performa seputar fungsi inline di React telah dikaitkan dengan bagaimana meneruskan callback baru pada setiap shouldComponentUpdatepengoptimalan render break di komponen turunan. ( dokumen )

Ini lebih sedikit tentang biaya pembuatan fungsi tambahan:

Masalah kinerja dengan Function.prototype.bind diperbaiki di sini dan fungsi panah adalah hal asli atau ditranslasikan oleh babel ke fungsi biasa; dalam kedua kasus tersebut, kami dapat berasumsi bahwa ini tidak lambat. ( Pelatihan React )

Saya yakin orang-orang yang mengklaim pembuatan fungsi mahal selalu salah informasi (tim React tidak pernah mengatakan ini). ( Tweet )

Kapan react/jsx-no-bindaturan itu berguna?

Anda ingin memastikan, bahwa komponen yang dimoisasi berfungsi sebagaimana mestinya:

  • React.memo (untuk komponen fungsi)
  • PureComponentatau custom shouldComponentUpdate(untuk komponen kelas)

Dengan mematuhi aturan ini, referensi objek fungsi stabil akan diteruskan. Jadi komponen di atas dapat mengoptimalkan kinerja dengan mencegah render ulang, bila alat peraga sebelumnya tidak berubah.

Bagaimana cara mengatasi kesalahan ESLint?

Kelas: Definisikan penangan sebagai metode, atau properti kelas untuk thispengikatan.
Hooks: Gunakan useCallback.

Middleground

Dalam banyak kasus, fungsi inline sangat nyaman digunakan dan sangat baik dalam hal persyaratan kinerja. Sayangnya, aturan ini tidak dapat dibatasi hanya untuk jenis komponen yang dimo. Jika Anda masih ingin menggunakannya secara menyeluruh, Anda dapat menonaktifkannya misalnya untuk simpul DOM sederhana:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
sumber