Caching Handler Peristiwa dan Reaksi Peningkatan Kinerja Aplikasi

Hari ini kami menerbitkan terjemahan materi, yang penulisnya, setelah menganalisis fitur bekerja dengan objek dalam JavaScript, menawarkan pengembang Bereaksi metodologi untuk mempercepat aplikasi. Secara khusus, kita berbicara tentang fakta bahwa variabel, yang, seperti yang mereka katakan, "ditugaskan objek", dan yang sering disebut hanya "objek", pada kenyataannya, tidak menyimpan objek itu sendiri, tetapi tautan ke sana. Fungsi dalam JavaScript juga objek, jadi hal di atas berlaku untuk mereka. Dengan mengingat hal ini, merancang komponen Bereaksi dan menganalisis secara kritis kode mereka dapat meningkatkan mekanisme internal dan meningkatkan kinerja aplikasi.



Fitur bekerja dengan objek dalam JavaScript


Jika Anda membuat beberapa fungsi yang terlihat persis sama dan mencoba membandingkannya, ternyata mereka, dari sudut pandang sistem, berbeda. Untuk memverifikasi ini, Anda dapat menjalankan kode berikut:

const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo; // false 

Sekarang mari kita coba untuk menetapkan variabel ke fungsi yang sudah ada yang ditugaskan ke variabel lain, dan membandingkan dua variabel ini:

 const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour; // true 

Seperti yang Anda lihat, dengan pendekatan ini, operator kesetaraan yang ketat mengembalikan true .
Objek secara alami berperilaku dengan cara yang sama:

 const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true 

Di sini kita berbicara tentang JavaScript, tetapi jika Anda memiliki pengalaman mengembangkan dalam bahasa lain, maka Anda mungkin akrab dengan konsep pointer. Dalam kode di atas, setiap kali sebuah objek dibuat, sebagian dari memori sistem dialokasikan untuk itu. Ketika kita menggunakan perintah form object1 = {} , ini mengarah ke mengisi dengan beberapa data sepotong memori yang dialokasikan khusus untuk object1 .

Sangat mungkin untuk membayangkan object1 sebagai alamat di mana struktur data yang terkait dengan objek berada di memori. Eksekusi perintah object2 = {} mengarah ke alokasi area memori lain, yang dirancang khusus untuk object2 . Apakah obect1 dan object2 di area memori yang sama? Tidak, masing-masing memiliki plot sendiri. Itulah sebabnya ketika kita mencoba membandingkan object1 dan object2 kita mendapatkan false . Objek-objek ini mungkin memiliki struktur yang identik, tetapi alamat dalam memori tempat mereka berada berbeda, dan itu adalah alamat yang diperiksa selama perbandingan.

Dengan mengeksekusi perintah object3 = object1 , kita menulis alamat object1 ke dalam object1 konstan. Ini bukan objek baru. Konstanta ini diberikan alamat objek yang ada. Anda dapat memverifikasi ini dengan:

 const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x; // false 

Dalam contoh ini, sebuah objek dibuat dalam memori dan alamatnya ditulis ke objek object1 . Kemudian alamat yang sama ditulis ke objek object3 . Mengubah object3 mengubah objek dalam memori. Ini berarti bahwa ketika mengakses objek menggunakan referensi lain untuk itu, misalnya, yang disimpan di object1 , kita sudah akan bekerja dengan versi yang dimodifikasi.

Fungsi, Objek, dan Bereaksi


Kesalahpahaman tentang mekanisme di atas oleh pengembang pemula sering menyebabkan kesalahan, dan, mungkin, pertimbangan fitur bekerja dengan objek layak untuk artikel yang terpisah. Namun, topik kita hari ini adalah kinerja aplikasi Bereaksi. Di bidang ini, kesalahan dapat dilakukan bahkan oleh pengembang yang cukup berpengalaman yang tidak memperhatikan bagaimana Bereaksi aplikasi dipengaruhi oleh fakta bahwa variabel JavaScript dan konstanta tidak disimpan dalam objek itu sendiri, tetapi hanya tautan ke mereka.

Apa hubungannya ini dengan Bereaksi? Bereaksi memiliki mekanisme cerdas untuk menghemat sumber daya sistem yang ditujukan untuk meningkatkan kinerja aplikasi: jika properti dan keadaan komponen tidak berubah, maka apa fungsi render tidak berubah. Jelas, jika komponennya tetap sama, tidak perlu dirender lagi. Jika tidak ada yang berubah, fungsi render akan kembali sama seperti sebelumnya, jadi tidak perlu untuk menjalankannya. Mekanisme ini membuat Bereaksi cepat. Sesuatu ditampilkan hanya jika perlu.

React memeriksa properti dan status komponen untuk kesetaraan menggunakan fitur JavaScript standar, yaitu, ia hanya membandingkannya menggunakan operator == . Bereaksi tidak melakukan perbandingan objek yang "dangkal" atau "dalam" untuk menentukan kesetaraannya. "Perbandingan dangkal" adalah konsep yang digunakan untuk menggambarkan perbandingan dari setiap pasangan kunci-nilai suatu objek, yang bertentangan dengan perbandingan di mana hanya alamat objek dalam memori yang dibandingkan (referensi untuk mereka). Perbandingan "dalam" objek bahkan lebih jauh, dan jika nilai-nilai properti yang dibandingkan dari objek juga objek, mereka juga membandingkan pasangan kunci-nilai dari objek-objek ini. Proses ini diulangi untuk semua objek yang bersarang di objek lain. Bereaksi tidak melakukan hal semacam itu, hanya memeriksa kesetaraan tautan.

Jika, misalnya, Anda mengubah properti komponen yang diwakili oleh objek bentuk { x: 1 } ke objek lain yang terlihat persis sama, Bereaksi akan merender ulang komponen, karena objek ini berada di area memori yang berbeda. Jika Anda mengingat contoh di atas, maka, ketika mengubah properti komponen dari object1 ke object1 , Bereaksi tidak akan merender ulang komponen seperti itu, karena konstanta object1 dan object1 merujuk ke objek yang sama.

Bekerja dengan fungsi-fungsi dalam JavaScript diatur dengan cara yang persis sama. Jika Bereaksi bertemu dengan fitur yang sama yang alamatnya berbeda, itu akan merender ulang. Jika "fungsi baru" hanyalah tautan ke fungsi yang telah digunakan, tidak akan ada rendering ulang.

Masalah khas saat bekerja dengan komponen


Berikut adalah salah satu skenario bekerja dengan komponen, yang, sayangnya, terus menerus muncul ketika saya memeriksa kode orang lain:

 class SomeComponent extends React.PureComponent { get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={() => alert('!')} />     </div>   ); } } 

Di depan kita adalah komponen yang sangat sederhana. Ini adalah tombol, ketika diklik, pemberitahuan ditampilkan. Di sebelah tombol ditampilkan instruksi untuk penggunaannya, memberi tahu pengguna apakah ia harus menekan tombol ini. Kontrol instruksi mana yang akan ditampilkan dengan mengatur properti do ( do={true} atau do={false} ) SomeComponent .

Setiap kali komponen SomeComponent -render ulang (ketika nilai properti do diubah dari true menjadi false dan sebaliknya), elemen Button juga dirender. Handler onClick , meskipun selalu sama, dibuat kembali setiap kali fungsi render dipanggil. Akibatnya, ternyata setiap kali komponen ditampilkan dalam memori, fungsi baru dibuat, karena pembuatannya dilakukan dalam fungsi render , tautan ke alamat baru di memori diteruskan ke <Button /> , dan komponen Button juga dirender lagi, meskipun faktanya dalam tidak ada yang berubah sama sekali.

Mari kita bicara tentang cara memperbaikinya.

Pemecahan masalah


Jika fungsi tidak tergantung pada komponen (konteks this ), maka Anda dapat mendefinisikannya di luar komponen. Semua instance komponen akan menggunakan referensi fungsi yang sama, karena dalam semua kasus itu akan menjadi fungsi yang sama. Begini tampilannya:

 const createAlertBox = () => alert('!'); class SomeComponent extends React.PureComponent { get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={createAlertBox} />     </div>   ); } } 

Berbeda dengan contoh sebelumnya, createAlertBox , dengan setiap panggilan untuk createAlertBox , akan berisi tautan yang sama ke area yang sama dalam memori. Akibatnya, output berulang Button tidak akan dieksekusi.

Sementara komponen Button kecil dan cepat ditampilkan, masalah yang dijelaskan di atas terkait dengan deklarasi fungsi internal juga dapat ditemukan dalam komponen besar dan kompleks yang membutuhkan banyak waktu untuk merendernya. Ini secara signifikan dapat memperlambat aplikasi Bereaksi. Dalam hal ini, masuk akal untuk mengikuti rekomendasi, yang dengannya fungsi-fungsi tersebut tidak boleh dideklarasikan di dalam metode render .

Jika fungsi tergantung pada komponen, yaitu, itu tidak dapat didefinisikan di luar itu, metode komponen dapat diteruskan sebagai pengendali peristiwa:

 class SomeComponent extends React.PureComponent { createAlertBox = () => {   alert(this.props.message); }; get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={this.createAlertBox} />     </div>   ); } } 

Dalam hal ini, dalam setiap instance SomeComponent ketika Anda mengklik tombol, berbagai pesan akan ditampilkan. Penangan event elemen Button harus unik untuk SomeComponent . Saat melewati metode cteateAlertBox , tidak masalah jika SomeComponent -render ulang. Tidak masalah jika properti message telah berubah. Alamat fungsi createAlertBox tidak berubah, yang berarti bahwa elemen Button tidak boleh dirender lagi. Berkat ini, Anda dapat menghemat sumber daya sistem dan meningkatkan kecepatan rendering aplikasi.

Semua ini bagus. Tetapi bagaimana jika fungsinya dinamis?

Memecahkan Masalah yang Lebih Kompleks


Penulis materi ini meminta Anda untuk memperhatikan fakta bahwa ia menyiapkan contoh-contoh di bagian ini, mengambil hal pertama yang muncul di benaknya, cocok untuk menggambarkan penggunaan kembali fungsi. Contoh-contoh ini dimaksudkan untuk membantu pembaca memahami esensi gagasan. Meskipun bagian ini direkomendasikan untuk membaca untuk memahami esensi dari apa yang terjadi, penulis menyarankan untuk memperhatikan komentar pada artikel asli , karena beberapa pembaca telah menyarankan versi yang lebih baik dari mekanisme yang dibahas di sini, yang mempertimbangkan fitur-fitur pembatalan cache dan mekanisme manajemen memori yang dibangun dalam React.

Jadi, sangat umum bahwa dalam satu komponen ada banyak penangan acara yang unik dan dinamis, misalnya, sesuatu yang serupa dapat dilihat dalam kode, di mana metode array map digunakan dalam metode render :

 class SomeComponent extends React.PureComponent { render() {   return (     <ul>       {this.props.list.map(listItem =>         <li key={listItem.text}>           <Button onClick={() => alert(listItem.text)} />         </li>       )}     </ul>   ); } } 

Di sini, sejumlah tombol yang berbeda akan ditampilkan dan sejumlah penangan acara yang berbeda akan dibuat, masing-masing diwakili oleh fungsi unik, dan, sebelumnya, ketika membuat SomeComponent , tidak diketahui apa fungsi-fungsi ini. Bagaimana cara memecahkan teka-teki ini?

Di sini memoisasi akan membantu kami, atau, lebih sederhana, caching. Untuk setiap nilai unik, buat fungsi dan masukkan ke dalam cache. Jika nilai unik ini terjadi lagi, itu akan cukup untuk mengambil dari cache fungsi yang sesuai dengannya, yang sebelumnya ditempatkan dalam cache.

Beginilah implementasi dari ide ini:

 class SomeComponent extends React.PureComponent { //    SomeComponent        //   . clickHandlers = {}; //       //    . getClickHandler(key) {   //       ,  .   if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) {     this.clickHandlers[key] = () => alert(key);   }   return this.clickHandlers[key]; } render() {   return (     <ul>       {this.props.list.map(listItem =>         <li key={listItem.text}>           <Button onClick={this.getClickHandler(listItem.text)} />         </li>       )}     </ul>   ); } } 

Setiap elemen array diproses oleh metode getClickHandler . Metode ini, pertama kali dipanggil dengan nilai tertentu, akan membuat fungsi yang unik untuk nilai ini, memasukkannya ke dalam cache dan mengembalikannya. Semua panggilan selanjutnya ke metode ini, dengan memberikan nilai yang sama dengannya, akan menyebabkannya mengembalikan tautan ke fungsi dari cache.

Akibatnya, rendering ulang SomeComponent tidak akan merender kembali Button . Demikian pula, menambahkan elemen ke properti list akan secara dinamis membuat pengendali acara untuk setiap tombol.

Anda harus kreatif dalam membuat pengidentifikasi unik untuk penangan jika mereka didefinisikan oleh lebih dari satu variabel, tetapi ini tidak jauh lebih rumit daripada pembuatan properti key unik untuk setiap objek JSX yang diperoleh sebagai hasil dari metode map .

Di sini saya ingin memperingatkan Anda tentang kemungkinan masalah menggunakan indeks array sebagai pengidentifikasi. Faktanya adalah bahwa dengan pendekatan ini, Anda dapat menemukan kesalahan jika urutan elemen dalam array berubah atau beberapa elemennya dihapus. Jadi, misalnya, jika pada awalnya array serupa tampak seperti [ 'soda', 'pizza' ] , dan kemudian berubah menjadi [ 'pizza' ] , dan Anda membuat cache event handler menggunakan perintah dari listeners[0] = () => alert('soda') form listeners[0] = () => alert('soda') , Anda akan menemukan bahwa ketika pengguna mengklik tombol di mana penangan dengan pengenal 0 ditugaskan dan yang, sesuai dengan isi array [ 'pizza' ] , harus menampilkan pesan pizza , pesan soda akan ditampilkan. Untuk alasan yang sama, tidak disarankan untuk menggunakan indeks array sebagai properti utama.

Ringkasan


Pada artikel ini, kami memeriksa fitur mekanisme JavaScript internal, dengan mempertimbangkan mana Anda dapat mempercepat rendering aplikasi Bereaksi. Kami berharap ide-ide yang disajikan di sini berguna.

Pembaca yang budiman! Jika Anda mengetahui cara menarik untuk mengoptimalkan Bereaksi aplikasi, silakan bagikan.

Source: https://habr.com/ru/post/id426053/


All Articles