Komponen Orde Tinggi dalam Bereaksi

Baru-baru ini, kami menerbitkan materi tentang fungsi tingkat tinggi dalam JavaScript yang ditujukan untuk mereka yang mempelajari JavaScript. Artikel yang kami terjemahkan hari ini dimaksudkan untuk pengembang Bereaksi pemula. Ini berfokus pada Komponen Tingkat Tinggi (HOC).



Prinsip KERING dan komponen pesanan lebih tinggi dalam Bereaksi


Anda tidak akan bisa maju cukup jauh dalam studi pemrograman dan tidak menemukan prinsip pengkultusan DRY (Jangan Ulangi Dirimu, jangan ulangi). Terkadang para pengikutnya bahkan melangkah terlalu jauh, tetapi, dalam banyak kasus, patut diperjuangkan untuk kepatuhan. Di sini kita akan berbicara tentang pola pengembangan Bereaksi paling populer, yang memastikan kepatuhan dengan prinsip KERING. Ini tentang komponen tingkat tinggi. Untuk memahami nilai komponen tingkat tinggi, pertama-tama mari kita merumuskan dan memahami masalah yang ingin mereka selesaikan.

Misalkan Anda perlu membuat ulang panel kontrol yang mirip dengan panel Stripe. Banyak proyek memiliki properti untuk dikembangkan sesuai dengan skema, ketika semuanya berjalan dengan baik sampai saat proyek selesai. Ketika Anda berpikir bahwa pekerjaan itu hampir selesai, Anda melihat bahwa panel kontrol memiliki banyak tooltips berbeda yang akan muncul ketika Anda mengarahkan kursor ke elemen-elemen tertentu.


Panel Kontrol dan Tip Alat

Untuk menerapkan fungsi tersebut, Anda dapat menggunakan beberapa pendekatan. Anda memutuskan untuk melakukan ini: tentukan apakah pointer berada di atas komponen individual, dan kemudian memutuskan apakah akan menunjukkan petunjuk untuk itu atau tidak. Ada tiga komponen yang perlu dilengkapi dengan fungsi serupa. Ini adalah Info , TrendChart , dan DailyChart .

Mari kita mulai dengan komponen Info . Sekarang ini adalah ikon SVG sederhana.

 class Info extends React.Component { render() {   return (     <svg       className="Icon-svg Icon--hoverable-svg"       height={this.props.height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   ) } } 

Sekarang kita perlu membuat komponen ini dapat menentukan apakah pointer mouse berada di atasnya atau tidak. Anda dapat menggunakan event mouse onMouseOut dan onMouseOut untuk ini. Fungsi yang diteruskan ke onMouseOver akan dipanggil jika pointer mouse jatuh ke area komponen, dan fungsi yang dilewatkan ke onMouseOut akan dipanggil ketika pointer meninggalkan komponen. Untuk mengatur semua ini dengan cara yang diterima di Bereaksi, kami menambahkan properti hovering ke komponen, yang disimpan di negara bagian, yang memungkinkan kami untuk merender ulang komponen dengan memperlihatkan atau menyembunyikan tooltip jika properti ini berubah.

 class Info extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id} />         : null}       <svg         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}         className="Icon-svg Icon--hoverable-svg"         height={this.props.height}         viewBox="0 0 16 16" width="16">           <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />       </svg>     </>   ) } } 

Ternyata cukup baik. Sekarang kita perlu menambahkan fungsionalitas yang sama ke dua komponen lagi - TrendChart dan DailyChart . Mekanisme di atas untuk komponen Info berfungsi dengan baik, apa yang tidak rusak tidak perlu diperbaiki, jadi mari kita buat yang sama di komponen lain menggunakan kode yang sama. Daur ulang kode untuk komponen TrendChart .

 class TrendChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id}/>         : null}       <Chart         type='trend'         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}       />     </>   ) } } 

Anda mungkin sudah mengerti apa yang harus dilakukan selanjutnya. Hal yang sama dapat dilakukan dengan komponen terakhir kami - DailyChart .

 class DailyChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id}/>         : null}       <Chart         type='daily'         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}       />     </>   ) } } 

Sekarang semuanya sudah siap. Anda mungkin sudah menulis sesuatu yang serupa di Bereaksi. Ini, tentu saja, bukan kode terburuk di dunia, tetapi tidak mengikuti prinsip KERING dengan baik. Seperti yang Anda lihat, dengan menganalisis kode komponen, kami, masing-masing, mengulangi logika yang sama.

Masalah yang menghadang kita sekarang harus menjadi sangat jelas. Ini adalah kode rangkap. Untuk mengatasinya, kami ingin menyingkirkan kebutuhan untuk menyalin kode yang sama dalam kasus-kasus di mana apa yang telah kami implementasikan diperlukan oleh komponen baru. Bagaimana cara mengatasinya? Sebelum kita berbicara tentang ini, kita akan fokus pada beberapa konsep pemrograman yang akan sangat memudahkan pemahaman tentang solusi yang diusulkan di sini. Kita berbicara tentang panggilan balik dan fungsi tingkat tinggi.

Fungsi Orde Tinggi


Fungsi dalam JavaScript adalah objek kelas satu. Ini berarti bahwa mereka, seperti objek, array atau string, dapat ditugaskan ke variabel, diteruskan ke fungsi sebagai argumen, atau dikembalikan dari fungsi lain.

 function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } addFive(10, add) // 15 

Jika Anda tidak terbiasa dengan perilaku ini, maka kode di atas mungkin tampak aneh bagi Anda. Mari kita bicarakan apa yang terjadi di sini. Yaitu, kami meneruskan fungsi add ke fungsi addFive sebagai argumen, ganti namanya menjadi addReference dan kemudian panggil.

Ketika konstruksi seperti itu digunakan, fungsi yang diteruskan sebagai argumen ke yang lain disebut panggilan balik (fungsi panggilan balik), dan fungsi yang menerima fungsi lain sebagai argumen disebut fungsi urutan yang lebih tinggi.

Memberi nama entitas dalam pemrograman adalah penting, jadi di sini adalah kode yang sama yang digunakan di mana nama diubah sesuai dengan konsep yang mereka wakili.

 function add (x,y) { return x + y } function higherOrderFunction (x, callback) { return callback(x, 5) } higherOrderFunction(10, add) 

Pola ini seharusnya tampak akrab bagi Anda. Faktanya adalah bahwa jika Anda menggunakan, misalnya, metode array JavaScript, bekerja dengan jQuery atau lodash, maka Anda sudah menggunakan fungsi dan panggilan balik tingkat tinggi.

 [1,2,3].map((i) => i + 5) _.filter([1,2,3,4], (n) => n % 2 === 0 ); $('#btn').on('click', () => console.log('Callbacks are everywhere') ) 

Mari kita kembali ke contoh kita. Bagaimana jika, alih-alih hanya membuat fungsi addFive , kami ingin membuat fungsi addTwenty , addTwenty , dan lainnya seperti itu. Mengingat bagaimana fungsi addFive , kita harus menyalin kodenya dan mengubahnya untuk membuat fungsi yang disebutkan di atas berdasarkan itu.

 function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } function addTen (x, addReference) { return addReference(x, 10) } function addTwenty (x, addReference) { return addReference(x, 20) } addFive(10, add) // 15 addTen(10, add) // 20 addTwenty(10, add) // 30 

Perlu dicatat bahwa kode kita tidak begitu mengerikan, tetapi jelas bahwa banyak fragmen di dalamnya diulang. Tujuan kami adalah agar kami dapat membuat sebanyak mungkin fungsi yang menambahkan angka-angka tertentu ke angka-angka yang diteruskan ( addFive , addTen , addTwenty , dan sebagainya) sebanyak yang kami butuhkan, sambil meminimalkan duplikasi kode. Mungkin untuk mencapai ini kita perlu membuat fungsi makeAdder ? Fungsi ini dapat mengambil nomor tertentu dan tautan ke fungsi add . Karena tujuan dari fungsi ini adalah untuk membuat fungsi baru yang menambahkan nomor yang diteruskan ke fungsi yang diberikan, kita dapat membuat fungsi makeAdder mengembalikan fungsi baru yang berisi angka tertentu (seperti angka 5 di makeFive ) dan dapat mengambil angka untuk menambah nomor itu.

Lihatlah contoh implementasi mekanisme di atas.

 function add (x, y) { return x + y } function makeAdder (x, addReference) { return function (y) {   return addReference(x, y) } } const addFive = makeAdder(5, add) const addTen = makeAdder(10, add) const addTwenty = makeAdder(20, add) addFive(10) // 15 addTen(10) // 20 addTwenty(10) // 30 

Sekarang kita dapat membuat banyak fungsi tambahan yang diperlukan, sambil meminimalkan jumlah duplikasi kode.

Jika ini menarik, konsep bahwa ada fungsi tertentu yang memproses fungsi lain sehingga mereka dapat digunakan dengan lebih sedikit parameter daripada sebelumnya disebut "aplikasi parsial fungsi". Pendekatan ini digunakan dalam pemrograman fungsional. Contoh penggunaannya adalah metode .bind yang digunakan dalam JavaScript.

Semua ini bagus, tetapi apa yang React lakukan dengan masalah di atas untuk menduplikasi kode untuk memproses acara mouse saat membuat komponen baru yang memerlukan fitur ini? Faktanya adalah seperti halnya fungsi orde tinggi makeAdder membantu kita meminimalkan duplikasi kode, apa yang disebut "komponen orde tinggi" akan membantu kita menangani masalah yang sama dalam aplikasi Bereaksi. Namun, di sini semuanya akan terlihat sedikit berbeda. Yaitu, alih-alih skema kerja, di mana fungsi tingkat tinggi mengembalikan fungsi baru yang memanggil panggilan balik, komponen urutan lebih tinggi dapat mengimplementasikan skema sendiri. Yaitu, ia dapat mengembalikan komponen baru yang menjadikan komponen yang memainkan peran sebagai "panggilan balik". Mungkin kita sudah mengatakan banyak hal, jadi sekarang saatnya untuk beralih ke contoh.

Fungsi urutan tertinggi kami


Fitur ini memiliki beberapa fitur berikut:

  • Dia adalah sebuah fungsi.
  • Dia menerima, sebagai argumen, panggilan balik.
  • Ini mengembalikan fungsi baru.
  • Fungsi yang dikembalikannya dapat memanggil panggilan balik asli yang diteruskan ke fungsi tingkat tinggi kami.

 function higherOrderFunction (callback) { return function () {   return callback() } } 

Komponen pesanan tertinggi kami


Komponen ini dapat dikarakterisasi sebagai berikut:

  • Ini adalah komponen.
  • Itu, sebagai argumen, mengambil komponen lain.
  • Ini mengembalikan komponen baru.
  • Komponen yang dikembalikannya dapat membuat komponen asli diteruskan ke komponen tingkat tinggi.

 function higherOrderComponent (Component) { return class extends React.Component {   render() {     return <Component />   } } } 

Implementasi HOC


Sekarang setelah kita, secara umum, telah mengetahui dengan tepat tindakan apa yang dilakukan oleh komponen tingkat tinggi, kita akan mulai membuat perubahan pada kode Bereaksi kita. Jika Anda ingat, inti dari masalah yang kami selesaikan adalah bahwa kode yang mengimplementasikan logika pemrosesan peristiwa mouse harus disalin ke semua komponen yang memerlukan fitur ini.

 state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) 

Mengingat hal ini, kita memerlukan komponen tingkat tinggi (sebut saja withHover ) untuk merangkum kode pemrosesan peristiwa mouse, dan kemudian meneruskan properti hovering ke komponen yang direndernya. Ini akan memungkinkan kami untuk mencegah duplikasi kode yang sesuai dengan menempatkannya di komponen withHover .

Pada akhirnya, inilah yang ingin kita capai. Setiap kali kita membutuhkan komponen yang perlu memiliki gagasan tentang properti hovering - hovering , kita dapat meneruskan komponen ini ke komponen orde tinggi withHover . Artinya, kami ingin bekerja dengan komponen seperti yang ditunjukkan di bawah ini.

 const InfoWithHover = withHover(Info) const TrendChartWithHover = withHover(TrendChart) const DailyChartWithHover = withHover(DailyChart) 

Kemudian, ketika apa dengan withHover diberikan, itu akan menjadi komponen sumber yang melewati properti hovering .

 function Info ({ hovering, height }) { return (   <>     {hovering === true       ? <Tooltip id={this.props.id} />       : null}     <svg       className="Icon-svg Icon--hoverable-svg"       height={height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   </> ) } 

Faktanya, sekarang kita hanya perlu mengimplementasikan komponen withHover . Dari uraian di atas, dapat dipahami bahwa ia harus melakukan tiga tindakan:

  • Bawa argumen ke Component.
  • Kembalikan komponen baru.
  • Berikan argumen Komponen dengan meneruskan properti hovering ke sana.

โ– Menerima argumen Komponen


 function withHover (Component) { } 

โ–Mengembalikan komponen baru


 function withHover (Component) { return class WithHover extends React.Component { } } 

โ– Rendering komponen Komponen dengan properti melayang diteruskan ke sana


Sekarang kita dihadapkan dengan pertanyaan berikut: bagaimana menuju ke properti yang hovering ? Bahkan, kami sudah menulis kode untuk bekerja dengan properti ini. Kita hanya perlu menambahkannya ke komponen baru, dan kemudian meneruskan properti hovering ke sana ketika merender komponen diteruskan ke komponen orde tinggi sebagai argumen Component .

 function withHover(Component) { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component hovering={this.state.hovering} />       </div>     );   } } } 

Saya lebih suka membicarakan hal-hal ini dengan cara berikut (seperti dokumentasi Bereaksi mengatakan demikian): komponen mengubah properti menjadi antarmuka pengguna, dan komponen tingkat tinggi mengubah komponen menjadi komponen lain. Dalam kasus kami, kami akan mengubah komponen Info , TrendChart dan DailyChart menjadi komponen baru yang, berkat properti hovering , tahu apakah pointer mouse berada di atas mereka.

Catatan tambahan


Pada titik ini, kami telah meninjau semua informasi dasar tentang komponen tingkat tinggi. Namun, ada beberapa hal yang lebih penting untuk dibahas.

Jika Anda melihat HOC kami dengan withHover , Anda akan melihat bahwa ia memiliki setidaknya satu titik lemah. Ini menyiratkan bahwa komponen penerima properti hovering tidak akan mengalami masalah dengan properti ini. Dalam kebanyakan kasus, asumsi ini cenderung dibenarkan, tetapi mungkin saja hal ini tidak dapat diterima. Misalnya, bagaimana jika komponen sudah memiliki properti hovering ? Dalam hal ini, akan ada tabrakan nama. Oleh karena itu, withHover dapat dilakukan pada komponen withHover , yang memungkinkan pengguna komponen ini untuk menentukan nama yang harus ditanggung oleh properti hovering ke komponen. Karena withHover hanyalah sebuah fungsi, mari kita menulis ulang sehingga withHover argumen kedua yang menetapkan nama properti untuk diteruskan ke komponen.

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } 

Sekarang, berkat mekanisme parameter default ES6, kami menetapkan nilai standar argumen kedua sebagai hovering , tetapi jika pengguna komponen withHover ingin mengubahnya, ia dapat memberikan, dalam argumen kedua ini, nama yang ia butuhkan.

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } function Info ({ showTooltip, height }) { return (   <>     {showTooltip === true       ? <Tooltip id={this.props.id} />       : null}     <svg       className="Icon-svg Icon--hoverable-svg"       height={height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   </> ) } const InfoWithHover = withHover(Info, 'showTooltip') 

Masalah dengan implementasi Arahkan


Anda mungkin telah memperhatikan masalah lain dengan implementasi withHover . Jika kami menganalisis komponen Info kami, Anda akan melihat bahwa, di antaranya, menerima properti height . Cara kita mengatur semuanya sekarang berarti bahwa height akan ditetapkan ke undefined . Alasan untuk ini adalah karena komponen withHover adalah komponen yang bertanggung jawab untuk memberikan apa yang diteruskan kepadanya sebagai argumen Component . Sekarang kami tidak mentransfer properti apa pun selain hovering kami buat ke komponen Component .

 const InfoWithHover = withHover(Info) ... return <InfoWithHover height="16px" /> 

Properti height diteruskan ke komponen InfoWithHover . Dan komponen apa ini? Ini adalah komponen yang kami kembali dari withHover .

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     console.log(this.props) // { height: "16px" }     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } 

Di dalam komponen WithHover this.props.height adalah 16px , tetapi di masa depan kami tidak akan melakukan apa pun dengan properti ini. Kita perlu membuat properti ini lolos ke argumen Component , yang kami render.

 render() {     const props = {       [propName]: this.state.hovering,       ...this.props,     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     ); } 

Tentang masalah bekerja dengan komponen pihak ketiga dari tingkat tertinggi


Kami percaya bahwa Anda telah menghargai keuntungan menggunakan komponen tingkat tinggi dalam menggunakan kembali logika di berbagai komponen tanpa perlu menyalin kode yang sama. Sekarang mari kita bertanya pada diri sendiri apakah ada kekurangan dalam komponen tingkat tinggi. Pertanyaan ini dapat dijawab secara positif, dan kami telah bertemu dengan kekurangan-kekurangan ini.

Saat menggunakan HOC, kontrol inversi terjadi. Bayangkan kita menggunakan komponen tingkat tinggi yang tidak dikembangkan oleh kita, seperti HOC withRouter React Router. Menurut dokumentasi, withRouter akan meneruskan properti match , location , dan history ke komponen yang dibungkus ketika merendernya.

 class Game extends React.Component { render() {   const { match, location, history } = this.props // From React Router   ... } } export default withRouter(Game) 

Harap perhatikan bahwa kami tidak membuat elemen Game (mis. - <Game /> ). Kami sepenuhnya mentransfer komponen React Router kami dan mempercayai komponen ini tidak hanya untuk merender, tetapi juga untuk meneruskan properti yang benar ke komponen kami. Kami telah mengalami masalah ini sebelumnya ketika kami berbicara tentang kemungkinan konflik nama ketika melewati properti hovering . Untuk memperbaikinya, kami memutuskan untuk mengizinkan pengguna HOC withHover menggunakan argumen kedua untuk mengonfigurasi nama properti terkait. Menggunakan HOC orang lain withRouter kami tidak memiliki kesempatan seperti itu. Jika properti match , location , atau history sudah digunakan dalam komponen Game , maka kami dapat mengatakan bahwa kami kurang beruntung. Yaitu, kita harus mengubah nama-nama ini di komponen kita, atau menolak untuk menggunakan HOC withRouter .

Ringkasan


Berbicara tentang HOC dalam Bereaksi, ada dua hal penting yang perlu diingat. Pertama-tama, HOC hanyalah sebuah pola. Komponen tingkat tinggi bahkan tidak bisa disebut sesuatu yang spesifik untuk Bereaksi, terlepas dari kenyataan bahwa mereka terkait dengan arsitektur aplikasi. Kedua, untuk mengembangkan aplikasi Bereaksi, Anda tidak perlu tahu tentang komponen tingkat tinggi. Anda mungkin tidak terbiasa dengan mereka, tetapi menulis program yang bagus. Namun, seperti dalam bisnis apa pun, semakin banyak alat yang Anda miliki, semakin baik hasil pekerjaan Anda. Dan, jika Anda menulis aplikasi menggunakan Bereaksi, Anda akan merugikan diri sendiri tanpa menambahkan HOC ke gudang senjata Anda.

Pembaca yang budiman! Apakah Anda menggunakan komponen pesanan lebih tinggi di Bereaksi?

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


All Articles