Bagaimana komponen Bereaksi fungsional berbeda dari komponen berbasis kelas?

Bagaimana komponen Bereaksi fungsional berbeda dari komponen berbasis kelas? Untuk beberapa waktu sekarang, jawaban tradisional untuk pertanyaan ini adalah: "Penggunaan kelas memungkinkan Anda untuk menggunakan sejumlah besar fitur komponen, misalnya, menyatakan." Sekarang, dengan munculnya kait , jawaban ini tidak lagi mencerminkan keadaan sebenarnya.

Anda mungkin pernah mendengar bahwa salah satu dari jenis komponen ini memiliki kinerja yang lebih baik daripada yang lain. Tapi yang mana? Sebagian besar tolok ukur yang menguji ini memiliki kekurangan , jadi saya akan menarik kesimpulan berdasarkan hasil mereka dengan sangat hati-hati. Kinerja terutama tergantung pada apa yang terjadi dalam kode, dan bukan pada apakah komponen fungsional atau komponen berbasis kelas dipilih untuk mengimplementasikan kemampuan tertentu. Studi kami menunjukkan bahwa perbedaan kinerja antara berbagai jenis komponen dapat diabaikan. Namun, perlu dicatat bahwa strategi optimasi yang digunakan untuk bekerja dengannya sedikit berbeda .



Bagaimanapun, saya tidak merekomendasikan penulisan ulang komponen yang ada menggunakan teknologi baru jika tidak ada alasan yang baik untuk itu, dan jika Anda tidak keberatan berada di antara mereka yang mulai menggunakan teknologi ini sebelum orang lain. Hooks masih merupakan teknologi baru (sama seperti React library pada 2014), dan beberapa "praktik terbaik" untuk aplikasi mereka belum dimasukkan dalam manual React.

Ke mana akhirnya kami sampai? Apakah ada perbedaan mendasar antara komponen fungsional React dan komponen berdasarkan kelas? Tentu saja ada perbedaan seperti itu. Ini adalah perbedaan dalam model mental menggunakan komponen tersebut. Dalam artikel ini saya akan mempertimbangkan perbedaan paling serius mereka. Itu sudah ada sejak, pada 2015, komponen fungsional muncul, tetapi sering diabaikan. Terdiri dari fakta bahwa komponen fungsional menangkap nilai yang diberikan. Mari kita bicara tentang apa artinya itu.

Perlu dicatat bahwa bahan ini bukan merupakan upaya untuk mengevaluasi komponen dari berbagai jenis. Saya hanya menjelaskan perbedaan antara dua model pemrograman dalam Bereaksi. Jika Anda ingin tahu lebih banyak tentang penggunaan komponen fungsional berdasarkan inovasi - rujuk ke daftar pertanyaan dan jawaban tentang kaitan ini.

Apa saja fitur kode komponen berdasarkan fungsi dan kelas?


Pertimbangkan komponen ini:

function ProfilePage(props) {  const showMessage = () => {    alert('Followed ' + props.user);  };  const handleClick = () => {    setTimeout(showMessage, 3000);  };  return (    <button onClick={handleClick}>Follow</button>  ); } 

Ini menampilkan tombol, yang, dengan menekan fungsi setTimeout , mensimulasikan permintaan jaringan, dan kemudian menampilkan kotak pesan yang mengkonfirmasi operasi. Misalnya, jika ' props.user 'Dan' disimpan di props.user , maka di jendela pesan, setelah tiga detik, 'Followed Dan' akan ditampilkan.

Perhatikan bahwa tidak masalah jika fungsi tanda panah atau deklarasi fungsi digunakan di sini. Konstruksi dari form function handleClick() akan bekerja dengan cara yang persis sama.

Bagaimana cara menulis ulang komponen ini sebagai kelas? Jika Anda baru saja mengulang kode yang baru saja diperiksa, mengubahnya menjadi kode komponen berdasarkan kelas, Anda mendapatkan yang berikut ini:

 class ProfilePage extends React.Component { showMessage = () => {   alert('Followed ' + this.props.user); }; handleClick = () => {   setTimeout(this.showMessage, 3000); }; render() {   return <button onClick={this.handleClick}>Follow</button>; } } 

Secara umum diterima bahwa dua fragmen kode tersebut adalah setara. Dan pengembang sering kali sepenuhnya bebas, dalam proses refactoring kode, mengubah satu menjadi yang lain, tanpa memikirkan konsekuensi yang mungkin terjadi.


Potongan-potongan kode ini tampaknya setara

Namun, ada sedikit perbedaan antara cuplikan kode ini. Lihatlah lebih dekat pada mereka. Lihat perbedaannya? Misalnya, saya tidak langsung melihatnya.

Selanjutnya kami akan mempertimbangkan perbedaan ini, oleh karena itu, bagi mereka yang ingin memahami esensi dari apa yang terjadi sendiri, contoh kerja dari kode ini.

Sebelum kita melanjutkan, saya ingin menekankan bahwa perbedaan dalam pertanyaan tidak ada hubungannya dengan React hooks. Dalam contoh sebelumnya, omong-omong, kait bahkan tidak digunakan. Ini tentang perbedaan antara fungsi dan kelas dalam Bereaksi. Dan jika Anda berencana untuk menggunakan banyak komponen fungsional dalam aplikasi Bereaksi Anda, maka Anda mungkin ingin memahami perbedaan ini.

Sebagai soal fakta, kami akan menggambarkan perbedaan antara fungsi dan kelas dengan contoh kesalahan yang sering dijumpai dalam aplikasi Bereaksi.

Kesalahan yang biasa terjadi pada Bereaksi aplikasi.


Buka halaman contoh yang menampilkan daftar yang memungkinkan Anda memilih profil pengguna, dan dua tombol Follow yang ditampilkan oleh ProfilePageClass dan ProfilePageClass , fungsional, dan berdasarkan kelas, kode yang ditunjukkan di atas.

Coba, untuk masing-masing tombol ini, untuk melakukan urutan tindakan berikut:

  1. Klik pada tombol.
  2. Ubah profil yang dipilih sebelum 3 detik berlalu setelah mengklik tombol.
  3. Baca teks yang ditampilkan di kotak pesan.

Setelah melakukan ini, Anda akan melihat fitur-fitur berikut:

  • Ketika Anda mengklik tombol yang dibentuk oleh komponen fungsional dengan profil Dan dipilih dan kemudian beralih ke profil Sophie , 'Followed Dan' akan ditampilkan di kotak pesan.
  • Jika Anda melakukan hal yang sama dengan tombol yang dibentuk oleh komponen berdasarkan kelas, 'Followed Sophie' akan ditampilkan.


Fitur Komponen Berbasis Kelas

Dalam contoh ini, perilaku komponen fungsional sudah benar. Jika saya berlangganan profil seseorang, dan kemudian beralih ke profil lain, komponen saya seharusnya tidak meragukan profil siapa saya berlangganan. Jelas, implementasi mekanisme yang dimaksud berdasarkan penggunaan kelas mengandung kesalahan (ngomong-ngomong, Anda harus menjadi pelanggan Sofia ).

Penyebab kerusakan komponen berbasis kelas


Mengapa komponen berbasis kelas berperilaku seperti ini? Untuk memahami ini, mari kita lihat metode showMessage di kelas kami:

 class ProfilePage extends React.Component { showMessage = () => {   alert('Followed ' + this.props.user); }; 

Metode ini membaca data dari this.props.user . Properti di Bereaksi tidak berubah, sehingga tidak berubah. Namun, this , seperti biasa, adalah entitas yang bisa berubah.

Sebenarnya, tujuan memiliki this di kelas terletak pada kemampuan this untuk berubah. Pustaka Bereaksi itu sendiri secara berkala melakukan mutasi this , yang memungkinkan bekerja dengan versi terbaru dari metode render dan metode siklus hidup komponen.

Akibatnya, jika komponen kami this.props ulang selama eksekusi permintaan, this.props akan berubah. Setelah itu, metode showMessage membaca nilai user dari entitas properti "terlalu baru".

Ini memungkinkan Anda untuk melakukan pengamatan menarik tentang antarmuka pengguna. Jika kita mengatakan bahwa antarmuka pengguna, secara konseptual, adalah fungsi dari kondisi aplikasi saat ini, maka pengendali event adalah bagian dari hasil render - seperti hasil render yang terlihat. Penangan acara kami "milik" operasi rendering tertentu bersama dengan properti dan keadaan tertentu.

Namun, penjadwalan batas waktu dengan callback this.props berbunyi melanggar koneksi ini. showMessage showMessage tidak "terikat" dengan operasi rendering tertentu, sebagai hasilnya, "kehilangan" properti yang benar. Membaca data dari this memutuskan koneksi ini.

Bagaimana, dengan menggunakan komponen berbasis kelas, untuk memecahkan masalah?


Bayangkan bahwa tidak ada komponen fungsional dalam Bereaksi. Lalu bagaimana mengatasi masalah ini?

Kami membutuhkan beberapa mekanisme untuk "mengembalikan" koneksi antara metode render dengan properti yang benar dan showMessage showMessage, yang membaca data dari properti. Mekanisme ini harus ditempatkan di suatu tempat di mana esensi props dengan data yang benar hilang.

Salah satu cara untuk melakukannya adalah dengan membaca this.props terlebih dahulu di event handler, dan kemudian secara eksplisit meneruskan apa yang dibacakan ke fungsi callback yang digunakan di setTimeout :

 class ProfilePage extends React.Component { showMessage = (user) => {   alert('Followed ' + user); }; handleClick = () => {   const {user} = this.props;   setTimeout(() => this.showMessage(user), 3000); }; render() {   return <button onClick={this.handleClick}>Follow</button>; } } 

Pendekatan ini berhasil . Namun konstruksi tambahan yang digunakan di sini, seiring waktu, akan mengarah pada peningkatan volume kode dan fakta bahwa kemungkinan kesalahan di dalamnya akan meningkat. Bagaimana jika kita membutuhkan lebih dari satu properti? Bagaimana jika kita juga perlu bekerja dengan negara? Jika metode showMessage metode lain dan metode ini membaca this.props.something atau this.state.something , maka kita akan kembali menghadapi masalah yang sama. Dan untuk menyelesaikannya, kita harus meneruskan this.props dan this.state sebagai argumen untuk semua metode yang dipanggil dari showMessage .

Jika itu benar untuk dilakukan, itu akan menghancurkan semua kenyamanan yang diberikan oleh komponen berdasarkan kelas. Kenyataan bahwa Anda perlu bekerja dengan metode dengan cara ini sulit untuk diingat, sulit untuk mengotomatisasi, sebagai akibatnya, pengembang sering, daripada menggunakan metode yang sama, setuju bahwa ada kesalahan dalam proyek mereka.

Demikian juga, menanamkan kode alert di handleClick tidak memecahkan masalah yang lebih global. Kita perlu menyusun kode sehingga dapat dibagi menjadi banyak metode, tetapi juga agar kita dapat membaca properti dan menyatakan yang berhubungan dengan operasi rendering yang terkait dengan panggilan tertentu. Masalah ini, omong-omong, bahkan tidak berlaku secara khusus untuk Bereaksi. Anda dapat memainkannya di perpustakaan apa pun untuk mengembangkan antarmuka pengguna, yang menempatkan data dalam objek yang bisa berubah seperti this .

Mungkin untuk mengatasi masalah ini, Anda dapat mengikat metode this di konstruktor?

 class ProfilePage extends React.Component { constructor(props) {   super(props);   this.showMessage = this.showMessage.bind(this);   this.handleClick = this.handleClick.bind(this); } showMessage() {   alert('Followed ' + this.props.user); } handleClick() {   setTimeout(this.showMessage, 3000); } render() {   return <button onClick={this.handleClick}>Follow</button>; } } 

Tapi ini tidak menyelesaikan masalah kita. Ingatlah bahwa kita membaca data dari this.props terlambat, dan tidak dalam sintaks yang digunakan! Namun, masalah ini akan teratasi jika kita mengandalkan penutupan JavaScript.

Pengembang sering mencoba menghindari penutupan, karena tidak mudah untuk memikirkan nilai-nilai yang, dari waktu ke waktu, tidak dapat bermutasi. Tetapi properti di Bereaksi tidak berubah! (Atau, minimal, ini sangat dianjurkan). Hal ini memungkinkan Anda untuk berhenti menganggap penutupan sebagai sesuatu karena programmer dapat, seperti yang mereka katakan, "menembak dirinya sendiri di kaki".

Ini berarti bahwa jika Anda “mengunci” properti atau keadaan operasi rendering tertentu dalam penutupan, Anda selalu dapat mengandalkannya untuk tidak berubah.

 class ProfilePage extends React.Component { render() {   //  !   const props = this.props;   //    ,      render.   //   -   .   const showMessage = () => {     alert('Followed ' + props.user);   };   const handleClick = () => {     setTimeout(showMessage, 3000);   };   return <button onClick={handleClick}>Follow</button>; } } 

Seperti yang Anda lihat, di sini kita “menangkap” properti selama panggilan ke metode render .


Properti ditangkap oleh panggilan render

Dengan pendekatan ini, kode apa pun yang ditemukan dalam metode render (termasuk showMessage ) dijamin untuk melihat properti yang ditangkap selama panggilan tertentu ke metode ini. Akibatnya, Bereaksi tidak lagi dapat menghentikan kita dari melakukan apa yang kita butuhkan.

Dalam metode render , Anda bisa mendeskripsikan sebanyak mungkin fungsi bantu yang Anda inginkan dan semuanya akan dapat menggunakan properti dan status "yang ditangkap". Inilah cara penutupan menyelesaikan masalah kami.

Analisis solusi untuk masalah menggunakan penutupan


Apa yang baru saja kami lakukan memungkinkan kami untuk menyelesaikan masalah , tetapi kode seperti itu terlihat aneh. Mengapa kelas diperlukan sama sekali jika fungsi dideklarasikan di dalam metode render , dan bukan sebagai metode kelas?

Kami, pada kenyataannya, dapat menyederhanakan kode ini dengan menyingkirkan "shell" dalam bentuk kelas yang mengelilinginya:

 function ProfilePage(props) { const showMessage = () => {   alert('Followed ' + props.user); }; const handleClick = () => {   setTimeout(showMessage, 3000); }; return (   <button onClick={handleClick}>Follow</button> ); } 

Di sini, seperti dalam contoh sebelumnya, properti ditangkap dalam fungsi, karena Bereaksi melewati mereka sebagai argumen. Tidak seperti this , Bereaksi tidak pernah props objek props .

Ini menjadi sedikit lebih jelas jika props dihancurkan dalam deklarasi fungsi:

 function ProfilePage({ user }) { const showMessage = () => {   alert('Followed ' + user); }; const handleClick = () => {   setTimeout(showMessage, 3000); }; return (   <button onClick={handleClick}>Follow</button> ); } 

Ketika komponen induk ProfilePage dengan properti lain, React akan memanggil fungsi ProfilePage . Tetapi event handler yang telah dipanggil milik panggilan sebelumnya ke fungsi ini, panggilan ini menggunakan nilai user sendiri dan showMessage showMessage sendiri, yang membaca nilai ini. Semua ini tetap tidak tersentuh.

Itulah sebabnya dalam versi asli dari contoh kita, ketika bekerja dengan komponen fungsional, memilih profil lain setelah mengklik tombol yang sesuai sebelum pesan ditampilkan tidak mengubah apa pun. Jika profil Sophie dipilih sebelum mengklik tombol, 'Followed Sophie' akan ditampilkan di jendela pesan, apa pun yang terjadi.


Menggunakan komponen fungsional

Perilaku ini benar (Anda mungkin juga ingin mendaftar untuk Sunil ).

Sekarang kita telah mengetahui apa perbedaan besar antara fungsi dan kelas di Bereaksi. Seperti yang telah disebutkan, kita berbicara tentang fakta bahwa komponen fungsional menangkap nilai. Sekarang mari kita bicara tentang kait.

Kait


Saat menggunakan kait, prinsip "menangkap nilai" berlaku untuk negara. Perhatikan contoh berikut:

 function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => {   alert('You said: ' + message); }; const handleSendClick = () => {   setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => {   setMessage(e.target.value); }; return (   <>     <input value={message} onChange={handleMessageChange} />     <button onClick={handleSendClick}>Send</button>   </> ); } 

Di sini Anda dapat bereksperimen dengannya

Meskipun ini bukan contoh terbaik dari antarmuka aplikasi pesan, proyek ini menggambarkan ide yang sama: jika pengguna mengirim pesan, komponen tidak boleh bingung tentang pesan mana yang dikirim. Konstanta message dari komponen fungsional ini menangkap status "milik" dari komponen yang menjadikan browser pengendali klik untuk tombol yang dipanggil. Akibatnya, message menyimpan apa yang ada di bidang input pada saat mengklik tombol Send .

Masalah menangkap properti dan status oleh komponen fungsional


Kita tahu bahwa komponen fungsional dalam Bereaksi, secara default, menangkap properti dan status. Tetapi bagaimana jika kita perlu membaca data terbaru dari properti atau negara bagian yang tidak termasuk pemanggilan fungsi tertentu? Bagaimana jika kita ingin " membacanya dari masa depan "?

Dalam komponen berbasis kelas, ini bisa dilakukan hanya dengan merujuk this.props atau this.state , karena this adalah entitas yang bisa berubah. Perubahannya terlibat dalam Bereaksi. Komponen fungsional juga dapat bekerja dengan nilai yang dapat diubah yang dibagi oleh semua komponen. Nilai-nilai ini disebut ref :

 function MyComponent() { const ref = useRef(null); //     `ref.current`. // ... } 

Namun, programmer perlu mengelola nilai-nilai tersebut secara mandiri.

Inti dari ref memainkan peran yang sama dengan bidang instance kelas. Ini adalah "pintu darurat" ke dunia imperatif yang bisa berubah. Anda mungkin akrab dengan konsep referensi DOM, tetapi ide ini jauh lebih umum. Ini dapat dibandingkan dengan kotak di mana programmer dapat meletakkan sesuatu.

Bahkan secara eksternal, konstruksi dari bentuk ini. this.something seperti cermin dari konstruksi something.current . Mereka adalah representasi dari konsep yang sama.

Secara default, Bereaksi tidak membuat entitas ref dalam komponen fungsional untuk nilai properti atau status terkini. Dalam banyak kasus, Anda tidak akan membutuhkannya, dan pembuatan otomatisnya akan membuang-buang waktu. Namun, bekerja dengan mereka, jika perlu, dapat diatur sendiri:

 function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => {   alert('You said: ' + latestMessage.current); }; const handleSendClick = () => {   setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => {   setMessage(e.target.value);   latestMessage.current = e.target.value; }; 

Jika kita membaca message di showMessage , maka kita akan melihat pesan yang ada di kolom pada saat mengklik tombol Send . Tetapi jika Anda membaca latestMessage.current , Anda bisa mendapatkan nilai terbaru - bahkan jika kami terus memasukkan teks di lapangan setelah mengklik tombol Send .

Anda dapat membandingkan ini dan contoh ini untuk mengevaluasi perbedaan secara independen. Nilai ref adalah cara "menghindari" keseragaman rendering, dalam beberapa kasus itu bisa sangat berguna.

Secara umum, Anda harus menghindari membaca atau menulis nilai ref selama proses rendering karena nilai-nilai ini bisa berubah. Kami berusaha agar rendering dapat diprediksi. Namun, jika kita perlu mendapatkan nilai terbaru dari sesuatu yang disimpan di properti atau dalam keadaan, memperbarui nilai ref secara manual bisa menjadi tugas yang membosankan. Itu dapat diotomatisasi menggunakan efek:

 function MessageThread() { const [message, setMessage] = useState(''); //    . const latestMessage = useRef(''); useEffect(() => {   latestMessage.current = message; }); const showMessage = () => {   alert('You said: ' + latestMessage.current); }; 

Ini adalah contoh yang menggunakan kode ini

Kami memberikan nilai di dalam efek, sebagai hasilnya, nilai ref hanya akan berubah setelah DOM diperbarui. Ini memastikan bahwa mutasi kami tidak mengganggu fitur seperti Time Slicing dan Suspense , yang bergantung pada kontinuitas operasi rendering.

Menggunakan nilai ref dengan cara ini tidak sering dibutuhkan. Menangkap sifat atau keadaan biasanya tampak sebagai pola perilaku sistem standar yang jauh lebih baik. Namun, ini bisa nyaman saat bekerja dengan API imperatif , seperti yang menggunakan interval atau langganan. Ingatlah bahwa Anda dapat bekerja dengan cara ini dengan nilai apa pun - dengan properti, dengan variabel yang disimpan di negara bagian, dengan seluruh objek props atau bahkan dengan fungsi.

Pola ini, di samping itu, mungkin berguna untuk keperluan optimasi. Misalnya, ketika sesuatu seperti useCallback berubah terlalu sering. Benar, solusi yang disukai adalah sering menggunakan peredam .

Ringkasan


Pada artikel ini, kami melihat salah satu pola yang salah untuk menggunakan komponen berbasis kelas dan berbicara tentang bagaimana menyelesaikan masalah ini dengan penutupan. Namun, Anda mungkin memperhatikan bahwa ketika Anda mencoba untuk mengoptimalkan kait dengan menentukan susunan dependensi, Anda mungkin mengalami kesalahan terkait dengan penutupan yang ketinggalan zaman. Apakah ini berarti bahwa kesalahan itu sendiri adalah masalah. Saya kira tidak.

Seperti yang ditunjukkan di atas, penutupan, pada kenyataannya, membantu kami memperbaiki masalah kecil yang sulit diperhatikan. Mereka, juga, membuatnya lebih mudah untuk menulis kode yang bekerja dengan benar secara paralel . Hal ini dimungkinkan karena fakta bahwa di dalam komponen, properti dan status yang benar dengan komponen yang diberikan ini "dikunci".

Dalam semua kasus yang saya lihat sejauh ini, masalah "penutupan usang" terjadi karena asumsi yang salah bahwa "fungsi tidak berubah", atau bahwa "properti selalu tetap sama". Saya harap setelah membaca materi ini, Anda yakin bahwa ini tidak benar.

Fungsi “menangkap” sifat dan statusnya - dan karenanya memahami fungsi mana yang dipertanyakan juga penting. Ini bukan kesalahan, itu adalah fitur komponen fungsional. Fungsi tidak boleh dikecualikan dari "array dependensi" untuk useEffect atau useCalback , misalnya. (Alat yang cocok untuk menyelesaikan masalah biasanya menggunakan useReducer atau useRef . Kami membicarakan hal ini di atas, dan segera kami akan menyiapkan bahan yang akan dikhususkan untuk pilihan pendekatan ini atau itu).

Jika sebagian besar kode dalam aplikasi kita akan didasarkan pada komponen fungsional, ini berarti bahwa kita perlu tahu lebih banyak tentang optimasi kode , dan nilai apa yang dapat berubah dari waktu ke waktu.

: « , , , , , ».

. , React , . , « », . , React .

, , .


React —

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


All Articles