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 setaraNamun, 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:
- Klik pada tombol.
- Ubah profil yang dipilih sebelum 3 detik berlalu setelah mengklik tombol.
- 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 KelasDalam 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() {
Seperti yang Anda lihat, di sini kita “menangkap” properti selama panggilan ke metode
render
.
Properti ditangkap oleh panggilan renderDengan 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 fungsionalPerilaku 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);
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('');
→
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 —