Nama saya Artyom Berezin, saya adalah pengembang beberapa layanan internal Yandex. Selama enam bulan terakhir, saya telah secara aktif bekerja dengan React Hooks. Dalam prosesnya, ada beberapa kesulitan yang harus dilawan. Sekarang saya ingin berbagi pengalaman ini dengan Anda. Dalam laporan tersebut, saya memeriksa API React Hook dari sudut pandang praktis - mengapa kita perlu kait, apakah layak untuk beralih, yang lebih baik untuk dipertimbangkan saat porting. Mudah untuk membuat kesalahan selama masa transisi, tetapi menghindarinya juga tidak begitu sulit.

- Kait hanyalah cara lain untuk menggambarkan logika komponen Anda. Ini memungkinkan Anda untuk menambahkan komponen fungsional ke beberapa fitur yang sebelumnya hanya melekat pada komponen di kelas.

Pertama-tama, itu adalah dukungan untuk keadaan internal, kemudian - dukungan untuk efek samping. Misalnya - permintaan jaringan atau permintaan ke WebSocket: berlangganan, berhenti berlangganan dari beberapa saluran. Atau, mungkin, kita berbicara tentang permintaan ke beberapa API browser asinkron atau sinkron lainnya. Selain itu, kait memberi kita akses ke siklus hidup komponen, ke awal kehidupannya, yaitu pemasangan, untuk memperbarui alat peraga dan kematiannya.

Mungkin cara termudah untuk menggambarkan sebagai perbandingan. Berikut adalah kode paling sederhana yang hanya dapat dengan komponen di kelas. Komponen mengubah sesuatu. Ini adalah penghitung biasa yang bisa ditambah atau dikurangi, hanya satu bidang dalam keadaan. Secara umum, saya pikir jika Anda terbiasa dengan Bereaksi, kodenya benar-benar jelas bagi Anda.

Komponen serupa yang melakukan fungsi yang persis sama, tetapi ditulis dalam kaitan, terlihat jauh lebih ringkas. Menurut perhitungan saya, rata-rata, ketika porting dari komponen di kelas ke komponen di kait, kode berkurang sekitar satu setengah kali, dan itu menyenangkan.
Beberapa kata tentang cara kait bekerja. Hook adalah fungsi global yang dideklarasikan di dalam React dan dipanggil setiap kali komponen dirender. Bereaksi melacak panggilan ke fungsi-fungsi ini dan dapat mengubah perilakunya atau memutuskan apa yang harus dikembalikan.

Ada beberapa batasan dalam penggunaan kait yang membedakannya dari fungsi biasa. Pertama-tama, mereka tidak dapat digunakan dalam komponen di kelas, hanya pembatasan seperti itu berlaku karena mereka tidak dibuat untuk mereka, tetapi untuk komponen fungsional. Kait tidak dapat disebut fungsi internal, loop dalam, kondisi. Hanya pada level pertama dari sarang, di dalam fungsi komponen. Pembatasan ini diberlakukan oleh React sendiri untuk dapat melacak kait mana yang dipanggil. Dan dia menumpuknya dalam urutan tertentu di otaknya. Kemudian, jika urutan ini tiba-tiba berubah atau beberapa kesalahan menghilang, kompleks, sulit dipahami, sulit untuk debug.
Tetapi jika Anda memiliki logika yang cukup rumit dan ingin menggunakan, misalnya, kait di dalam kait, maka kemungkinan besar ini adalah tanda bahwa Anda harus membuat kait. Misalkan Anda membuat beberapa kait yang terhubung satu sama lain dalam kait kustom terpisah. Dan di dalamnya Anda dapat menggunakan kait kustom lainnya, sehingga membangun hierarki kait, menyoroti logika umum di sana.

Kait memberikan beberapa keunggulan dibandingkan kelas. Pertama-tama, sebagai berikut dari yang sebelumnya, menggunakan pengait kustom, Anda dapat meraba-raba logika lebih mudah. Sebelumnya, menggunakan pendekatan dengan Komponen Orde Tinggi, kami meletakkan beberapa jenis logika bersama, dan itu adalah pembungkus komponen. Sekarang kita menempatkan logika ini di dalam kait. Dengan demikian, pohon komponen berkurang: sarangnya berkurang, dan menjadi lebih mudah bagi Bereaksi untuk melacak perubahan komponen, menghitung ulang pohon, menghitung ulang DOM virtual, dll. Ini memecahkan masalah yang disebut wrapper-hell. Mereka yang bekerja dengan Redux, saya pikir, sudah familiar dengan ini.
Kode yang ditulis menggunakan kait jauh lebih mudah untuk diminimalkan dengan minimizers modern seperti Terser atau UglifyJS lama. Faktanya adalah kita tidak perlu menyimpan nama metode, kita tidak perlu memikirkan prototipe. Setelah transpilasi, jika targetnya adalah ES3 atau ES5, kami biasanya mendapatkan banyak prototipe yang ditambal. Di sini semua ini tidak perlu dilakukan, oleh karena itu lebih mudah untuk diminimalkan. Dan, sebagai akibat dari tidak menggunakan kelas, kita tidak perlu memikirkan hal ini. Untuk pemula, ini sering merupakan masalah besar dan mungkin salah satu alasan utama bug: kita lupa bahwa ini mungkin sebuah jendela, bahwa kita perlu mengikat metode, misalnya, di konstruktor atau dengan cara lain.
Selain itu, penggunaan kait memungkinkan Anda untuk menyoroti logika yang mengontrol efek satu sisi. Sebelumnya, logika ini, terutama ketika kita memiliki beberapa efek samping untuk suatu komponen, harus dibagi menjadi berbagai metode siklus hidup komponen. Dan, sejak kait minimisasi muncul, React.memo muncul, sekarang komponen fungsional meminjamkan diri ke memoisasi, yaitu komponen ini tidak akan dibuat kembali atau diperbarui bersama kami jika propsnya tidak berubah. Ini tidak dapat dilakukan sebelumnya, sekarang mungkin. Semua komponen fungsional dapat dibungkus dengan memo. Juga di dalam hook useMemo muncul, yang dapat kita gunakan untuk menghitung beberapa nilai berat, atau instantiate beberapa kelas utilitas hanya sekali.
Laporan tidak akan lengkap jika saya tidak berbicara tentang beberapa kait dasar. Pertama-tama, ini adalah kait manajemen negara.

Pertama-tama - useState.

Contohnya mirip dengan yang ada di awal laporan. useState adalah fungsi yang mengambil nilai awal, dan mengembalikan tuple dari nilai saat ini dan fungsi untuk mengubah nilai itu. Semua sihir dilayani oleh Bereaksi internal. Kita bisa membaca nilai ini atau mengubahnya.
Tidak seperti kelas, kita dapat menggunakan objek negara sebanyak yang kita butuhkan, memecah negara menjadi potongan-potongan logis agar tidak mencampurnya dalam satu objek, seperti dalam kelas. Dan potongan-potongan ini akan sepenuhnya terisolasi satu sama lain: mereka dapat diubah secara independen satu sama lain. Hasilnya, misalnya, dari kode ini: kita mengubah dua variabel, menghitung tombol hasil dan tampilan yang memungkinkan kita untuk mengubah variabel pertama di sana-sini, dan variabel kedua di sana-sini. Ingat contoh ini, karena nanti kita akan melakukan hal serupa, tetapi jauh lebih rumit.

Ada semacam penggunaan steroid untuk pecinta Redux. Ini memungkinkan Anda untuk lebih konsisten mengubah status menggunakan peredam. Saya pikir mereka yang akrab dengan Redux bahkan tidak bisa menjelaskan, bagi mereka yang tidak terbiasa, saya akan katakan.
Peredam adalah fungsi yang menerima keadaan, dan objek, biasanya disebut tindakan, yang menjelaskan bagaimana keadaan ini harus berubah. Lebih tepatnya, ia melewati beberapa parameter, dan di dalam peredam itu sudah memutuskan, tergantung pada parameter mereka, bagaimana negara akan berubah, dan sebagai hasilnya, negara baru harus dikembalikan, diperbarui.

Dalam kira-kira cara ini digunakan dalam kode komponen. Kami memiliki hook useReducer, dibutuhkan fungsi reducer, dan parameter kedua adalah nilai awal state. Kembali, seperti useState, keadaan saat ini, dan fungsi untuk mengubahnya adalah pengiriman. Jika Anda melewatkan objek tindakan untuk dikirim, kami akan meminta perubahan status.

Sangat penting gunakan pengait efek. Ini memungkinkan Anda untuk menambahkan efek samping pada komponen, memberikan alternatif bagi siklus hidup. Dalam contoh ini, kami menggunakan metode sederhana dengan useEffect: itu hanya meminta beberapa data dari server, dengan API, misalnya, dan menampilkan data ini di halaman.

UseEffect memiliki mode lanjutan, ini adalah ketika fungsi yang dilewati untuk menggunakanEffect mengembalikan beberapa fungsi lainnya, maka fungsi ini akan dipanggil pada loop berikutnya, ketika useEffect ini akan diterapkan.
Saya lupa menyebutkan, useEffect disebut tidak sinkron, tepat setelah perubahan diterapkan ke DOM. Artinya, itu menjamin bahwa itu akan dieksekusi setelah komponen diberikan, dan dapat menyebabkan render berikutnya jika beberapa nilai berubah.

Di sini kita bertemu untuk pertama kalinya dengan konsep dependensi. Beberapa kait - useEffect, useCallback, useMemo - mengambil array nilai sebagai argumen kedua, yang akan memungkinkan kita untuk mengatakan apa yang harus dilacak. Perubahan pada array ini menyebabkan beberapa jenis efek. Sebagai contoh, di sini, secara hipotesis, kami memiliki beberapa jenis komponen untuk memilih penulis dari beberapa daftar. Dan sepiring dengan buku oleh penulis ini. Dan ketika penulis berubah, useEffect akan dipanggil. Ketika authorId ini diubah, permintaan akan dipanggil dan buku akan dimuat.
Juga, saya akan menyebutkan dalam melewati kait seperti useRef, ini adalah alternatif untuk React.createRef, sesuatu yang mirip dengan useState, tetapi perubahan pada ref tidak mengarah ke rendering. Terkadang nyaman untuk beberapa peretasan. useImperativeHandle memungkinkan kita untuk mendeklarasikan "metode publik" tertentu pada komponen. Jika Anda menggunakan useRef di komponen induk, maka itu dapat menarik metode ini. Sejujurnya, saya mencobanya sekali untuk tujuan pendidikan, dalam praktiknya itu tidak berguna. useContext hanya hal yang baik, ini memungkinkan Anda untuk mengambil nilai saat ini dari konteks jika penyedia telah menetapkan nilai ini di tempat yang lebih tinggi di tingkat hierarki.
Ada satu cara untuk mengoptimalkan Bereaksi aplikasi pada kait, ini adalah memoisasi. Memoisasi dapat dibagi menjadi internal dan eksternal. Pertama tentang luar.

Ini adalah React.memo, praktis merupakan alternatif dari kelas React.PureComponent, yang melacak perubahan props dan mengubah komponen hanya ketika props atau status berubah.
Namun, di sini, hal serupa, tanpa negara. Itu juga memonitor perubahan dalam alat peraga, dan jika alat peraga telah berubah, renderer terjadi. Jika alat peraga tidak berubah, komponen tidak diperbarui, dan kami menghemat ini.

Metode optimasi internal. Pertama-tama, ini adalah hal yang agak rendah - useMemo, jarang digunakan. Ini memungkinkan Anda untuk menghitung beberapa nilai, dan menghitung ulang hanya jika nilai yang ditentukan dalam dependensi telah berubah.

Ada kasus khusus useMemo untuk fungsi yang disebut useCallback. Ini terutama digunakan untuk memoize nilai fungsi event handler yang akan diteruskan ke komponen turunan sehingga komponen turunan ini tidak dapat dirender lagi. Ini digunakan secara sederhana. Kami menjelaskan fungsi tertentu, membungkusnya dalam useCallback, dan menunjukkan variabel mana yang bergantung padanya.
Banyak orang memiliki pertanyaan, tetapi apakah kita memerlukan ini? Apakah kita perlu kait? Apakah kita bergerak atau tetap seperti sebelumnya? Tidak ada jawaban tunggal, semuanya tergantung pada preferensi. Pertama-tama, jika Anda secara langsung terikat pada pemrograman berorientasi objek, jika komponen Anda, Anda terbiasa menggunakannya sebagai kelas, mereka memiliki metode yang dapat ditarik, maka, mungkin, hal ini mungkin tampak berlebihan bagi Anda. Pada prinsipnya, bagiku ketika aku pertama kali mendengar tentang kait yang terlalu rumit, semacam sihir ditambahkan, dan tidak jelas mengapa.
Bagi pecinta fungsi, ini, katakanlah, harus dimiliki, karena kait adalah fungsi, dan teknik pemrograman fungsional berlaku untuk mereka. Misalnya, Anda dapat menggabungkannya atau melakukan apa saja, menggunakan, misalnya, perpustakaan seperti Ramda, dan sejenisnya.

Karena kita menyingkirkan kelas, kita tidak perlu lagi mengikat konteks ini dengan metode. Jika Anda menggunakan metode ini sebagai panggilan balik. Biasanya, ini adalah masalah, karena Anda harus ingat untuk mengikatnya di konstruktor, atau menggunakan ekstensi tidak resmi dari sintaks bahasa, panah-berfungsi sebagai properti. Latihan yang cukup umum. Saya menggunakan dekorator saya, yang juga, pada prinsipnya, secara eksperimen, tentang metode.

Ada perbedaan dalam cara siklus hidup bekerja, bagaimana mengelolanya. Kait mengaitkan hampir semua tindakan siklus hidup dengan hook useEffect, yang memungkinkan Anda untuk berlangganan kelahiran dan pembaruan komponen dan kematiannya. Di kelas, untuk ini, kami harus mendefinisikan ulang beberapa metode, seperti componentDidMount, componentDidUpdate, dan componentWillUnmount. Selain itu, metode shouldComponentUpdate sekarang dapat diganti dengan React.memo.

Ada perbedaan yang cukup kecil dalam cara penanganan negara. Pertama, kelas memiliki satu objek state. Kami harus menjejalkan apa pun di sana. Dalam kaitan, kita dapat memecah keadaan logis menjadi beberapa bagian, yang akan nyaman bagi kita untuk beroperasi secara terpisah.
setState () komponen pada kelas yang diizinkan untuk menentukan tambalan keadaan, sehingga mengubah satu atau beberapa bidang negara. Dalam kaitan, kita harus mengubah seluruh negara secara keseluruhan, dan ini bahkan bagus, karena modis untuk menggunakan segala macam hal yang tidak dapat diubah dan tidak pernah mengharapkan objek kita bermutasi. Mereka selalu baru dengan kita.
Fitur utama dari kelas yang tidak dimiliki kait: kita bisa berlangganan perubahan negara. Artinya, kami mengubah negara, dan segera berlangganan perubahannya, memproses sesuatu secara imperatif segera setelah perubahan diterapkan. Dalam kaitan, ini tidak berfungsi. Ini perlu dilakukan dengan cara yang sangat menarik, saya akan memberi tahu Anda lebih lanjut.
Dan sedikit tentang cara fungsional memperbarui. Ini berfungsi baik di sana dan di sana, ketika fungsi perubahan negara menerima fungsi lain, yang seharusnya tidak diubah oleh kondisi ini, melainkan dibuat. Dan jika dalam kasus komponen kelas dapat mengembalikan beberapa jenis tambalan kepada kami, maka di kait kita harus mengembalikan nilai yang sama sekali baru.
Secara umum, Anda tidak mungkin mendapatkan jawaban apakah akan pindah atau tidak. Tetapi saya menyarankan setidaknya untuk mencoba, setidaknya untuk kode baru, untuk merasakannya. Ketika saya baru saja mulai bekerja dengan kait, saya segera mengidentifikasi beberapa kait khusus yang sesuai untuk proyek saya. Pada dasarnya, saya mencoba mengganti beberapa fitur yang telah saya terapkan melalui Komponen Orde Tinggi.

useDismounted - bagi mereka yang terbiasa dengan RxJS, ada peluang untuk berhenti berlangganan secara besar-besaran dari semua Yang Dapat Diamati dalam satu komponen tunggal, atau dalam satu fungsi, dengan berlangganan setiap Observable ke objek khusus, Subjek, dan ketika ditutup, semua langganan dibatalkan. Ini sangat nyaman jika komponennya kompleks, jika ada banyak operasi tidak sinkron di dalam Observable, akan lebih mudah untuk berhenti berlangganan sekaligus, dan tidak dari masing-masing secara terpisah.
useObservable mengembalikan nilai dari Observable ketika yang baru muncul di sana. Hook useBehaviourSubject yang serupa kembali dari BehaviourSubject. Perbedaannya dari Observable adalah bahwa ia awalnya memiliki beberapa makna.
Penggunaan hook kustom nyaman nyamanDircedValue memungkinkan kami untuk mengatur, misalnya, sujest untuk string pencarian, sehingga tidak setiap kali Anda menekan tombol, mengirim sesuatu ke server, tetapi tunggu sampai pengguna selesai mengetik.
Dua kait serupa. useWindowResize mengembalikan nilai aktual saat ini untuk ukuran jendela. Hook berikutnya untuk posisi gulir adalah useWindowScroll. Saya menggunakannya untuk menceritakan beberapa jendela pop-up atau modal, jika ada hal-hal rumit yang tidak dapat dilakukan dengan CSS.
Dan sebuah pengait kecil untuk menerapkan kunci pintas, yang komponennya, ketika ada pada halaman, ia berlangganan beberapa kunci pintas. Ketika dia meninggal, berhenti berlangganan otomatis terjadi.
Untuk kenyamanan apa kait ini? Bahwa kami dapat menjejalkan berhenti berlangganan di dalam kait, dan kami tidak harus memikirkan berhenti berlangganan secara manual di suatu tempat di komponen tempat kait ini digunakan.
Belum lama ini, mereka memberi saya tautan ke pustaka penggunaan reaksi, dan ternyata sebagian besar pengait kustom ini sudah diterapkan di sana. Dan saya menulis sepeda. Ini kadang-kadang berguna, tetapi di masa depan, kemungkinan besar, saya mungkin akan membuangnya dan menggunakan reaksi digunakan. Dan saya menyarankan Anda untuk juga melihat apakah Anda ingin menggunakan kait.

Sebenarnya, tujuan utama dari laporan ini adalah untuk menunjukkan cara menulis yang salah, masalah apa yang bisa terjadi dan bagaimana cara menghindarinya. Hal pertama, mungkin apa yang dipelajari orang ini dan mencoba menulis sesuatu, adalah menggunakan useEffect secara tidak benar. Berikut adalah kode yang mirip dengan yang ditulis oleh 100% setiap orang jika mereka mencoba kait. Hal ini disebabkan oleh fakta bahwa useEffect pada awalnya dirasakan secara mental, sebagai alternatif untuk componentDidMount. Tapi, tidak seperti componentDidMount, yang dipanggil hanya sekali, useEffect dipanggil pada setiap render. Dan kesalahan di sini adalah ia berubah, katakanlah, variabel data, dan pada saat yang sama mengubahnya mengarah ke penyaji komponen, sebagai akibatnya, efeknya akan diminta kembali. Dengan demikian, kami mendapatkan serangkaian permintaan AJAX tanpa akhir ke server, dan komponen itu sendiri terus memperbarui, memperbarui, memperbarui.

Memperbaikinya sangat sederhana. Anda perlu menambahkan di sini array kosong dari dependensi-dependensi itu di mana ia bergantung, dan perubahan yang akan me-restart efek. Jika kami memiliki daftar dependensi kosong yang ditentukan di sini, maka efeknya, karenanya, tidak akan dimulai kembali. Ini bukan semacam peretasan, ini adalah fitur dasar menggunakan useEffect.

Katakanlah kita memperbaikinya. Sekarang sedikit rumit. Kami memiliki komponen yang membuat sesuatu yang perlu diambil dari server untuk beberapa jenis ID. Dalam hal ini, pada prinsipnya, semuanya berfungsi dengan baik sampai kami mengubah entitasId di induknya, mungkin ini tidak relevan untuk komponen Anda.

Tetapi kemungkinan besar, jika itu berubah atau ada kebutuhan untuk mengubahnya, dan Anda memiliki komponen lama di halaman Anda dan ternyata itu tidak memperbarui, lebih baik menambahkan entitasId di sini, sebagai ketergantungan, menyebabkan pembaruan, memperbarui data.

Contoh yang lebih kompleks dengan useCallback. Di sini, pada pandangan pertama, semuanya baik-baik saja. Kami memiliki halaman tertentu yang memiliki semacam penghitung waktu mundur, atau, sebaliknya, penghitung waktu yang baru saja berdetak. Dan, misalnya, daftar host, dan di atas adalah filter yang memungkinkan Anda untuk memfilter daftar host ini. Nah, pemeliharaan telah ditambahkan di sini hanya untuk menggambarkan nilai yang sering berubah yang diterjemahkan menjadi renderer.
, , maintenance , , , onChange. onChange, . , HostFilters - , , dropdown, . , . , .

onChange useCallback. , .
, . , , . Facebook, React. , , , , '. , , confusing .

? — , - , , , , , . .
, , , , , , . , Garbage Collector , . , , , , . , , , reducer, , . , .
, , . - , , setValue - , , setState . - useEffect.
useEffect - , - , , , useEffect. useEffect , . , , Backbone, : , , , - . , , - , . - . , , , , - . , , , , , , . .
, , . , , . , . , . , , , dropdown . , . dropdown pop-up, useWindowScroll, useWindowResize , . , , — , .
, , . , , , , , . , , , , , .

, «», . , , TypeScript . . , reducer Redux , action. , action , action. , , , .
. , action. , , IncrementA 0, 1, 2, . . , , , , . action action, - . UnionType “Action”, , , action. .
— . , initialState, . , - . TypeScript. . , typeState , initialState.

reducer. State, Action, : switch action.type. TypeScript UnionType: case, - , type. action .
, : , , . .

? , . . , reducer. , action creator , , dispatch.

extension Dev Tools. . .
, , . , , . useDebugValue , - Dev Tool. useConstants, - , loaded, , , .

— . , . , . , , , . , , — - , — .
. Facebook ESLint, . , , . , dependencies . , , , .
, , , - , . , , , . . , - - .
— , , - . , , . , , - . , . . :
- «React hooks — ?» C . , , , , .
- useEffect . , , , , , . .
- «useReducer vs useState in React» , useReducer, useState. : , , , useReducer. - , useState .
- React Hooks CheatSheets c .
- . Usehooks.com — , . . react-use — , , .