Menulis Komponen yang Dapat Digunakan Kembali Menghormati SOLID

Halo semuanya! Nama saya Roma, saya frontend di Ya Tutorial. Hari ini saya akan memberi tahu Anda cara menghindari duplikasi kode dan menulis komponen berkualitas tinggi yang dapat digunakan kembali. Artikel ini ditulis berdasarkan (tetapi hanya dengan alasan!) Dari laporan Y. Subbotnik - ada video di akhir posting. Jika Anda tertarik untuk memahami topik ini, selamat datang di bawah cat.



Artikel tersebut berisi analisis yang lebih rinci tentang prinsip-prinsip dan contoh-contoh rinci dari praktek yang tidak sesuai dengan laporan. Saya sarankan membacanya jika Anda ingin menyelami topik lebih dalam dan mempelajari bagaimana kami menulis komponen yang dapat digunakan kembali. Jika Anda ingin berkenalan dengan dunia komponen yang dapat digunakan kembali secara umum, maka menurut saya, rekaman laporan lebih cocok untuk Anda.






Semua orang tahu bahwa duplikasi kode itu buruk karena sering dibicarakan: di buku tentang bahasa pemrograman pertama Anda, di kursus pemrograman, di buku tentang penulisan kode berkualitas seperti Perfect Code dan Clean Code.



Mari kita lihat mengapa sangat sulit untuk menghindari duplikasi di frontend dan bagaimana menulis komponen yang dapat digunakan kembali dengan benar. Dan prinsip-prinsip SOLID akan membantu kita.



Mengapa sulit untuk menghentikan duplikasi kode?



Tampaknya prinsipnya terdengar sederhana. Dan pada saat yang sama, mudah untuk memeriksa apakah itu dihormati: jika tidak ada duplikasi dalam basis kode, maka semuanya baik-baik saja. Mengapa begitu sulit dalam praktiknya?



Mari kita menganalisis contoh dengan perpustakaan komponen Ya Tutorial. Sekali waktu, proyek itu adalah monolit. Kemudian, untuk kenyamanan, pengembang memutuskan untuk memindahkan komponen yang dapat digunakan kembali ke perpustakaan terpisah. Salah satu yang pertama sampai di sana adalah komponen tombol. Komponen telah berevolusi, seiring waktu, "keterampilan" baru dan pengaturan tombol telah muncul, jumlah penyesuaian visual telah meningkat. Setelah beberapa waktu, komponen menjadi sangat canggih sehingga menjadi tidak nyaman untuk digunakan untuk tugas baru dan berkembang lebih jauh.



Jadi, pada iterasi berikutnya, salinan komponen muncul - Button2. Ini terjadi sejak lama, tidak ada yang ingat alasan pasti kemunculannya. Namun, komponen itu dibuat.







Tampaknya tidak apa-apa - biarkan ada dua tombol. Lagi pula, itu hanya sebuah tombol. Namun, pada kenyataannya, memiliki dua tombol dalam sebuah proyek memiliki konsekuensi jangka panjang yang sangat tidak menyenangkan.



Setiap kali, ketika perlu memperbarui gaya, tidak jelas komponen mana yang harus melakukan ini. Saya harus memeriksa di mana komponen mana yang digunakan agar tidak secara tidak sengaja merusak gaya di tempat lain. Ketika versi baru dari tampilan tombol muncul, kami memutuskan komponen mana yang akan diperluas. Setiap kali kami melihat fitur baru, kami memikirkan tombol mana yang akan digunakan. Dan terkadang di satu tempat kami membutuhkan beberapa tombol yang berbeda, lalu kami mengimpor dua komponen tombol ke dalam satu komponen proyek sekaligus.



Terlepas dari kenyataan bahwa dalam jangka panjang keberadaan dua komponen tombol itu ternyata menyakitkan, kami tidak segera memahami keseriusan masalah dan berhasil melakukan sesuatu yang mirip dengan ikon. Kami membuat komponen, dan ketika kami menyadari bahwa itu sangat tidak nyaman bagi kami, kami membuat Ikon2, dan ketika ternyata tidak cocok untuk tugas baru, kami menulis Ikon3.



Hampir seluruh rangkaian efek negatif dari duplikasi tombol diulang di komponen ikon. Itu sedikit lebih mudah karena ikon lebih jarang digunakan dalam proyek. Meskipun, sejujurnya, itu semua tergantung pada fiturnya. Selain itu, baik untuk tombol dan ikon, komponen lama tidak dihapus saat membuat yang baru, karena penghapusan memerlukan banyak pemfaktoran ulang dengan kemungkinan munculnya bug di seluruh proyek. Jadi apa kesamaan tombol dan ikon kasus? Skema yang sama untuk tampilan duplikat dalam proyek. Sulit bagi kami untuk menggunakan kembali komponen saat ini, menyesuaikannya dengan kondisi baru, jadi kami membuat yang baru.



Dengan membuat duplikat komponen, kita memperumit kehidupan masa depan kita. Kami ingin merakit antarmuka dari blok yang sudah jadi, seperti konstruktor. Untuk melakukan ini dengan nyaman, Anda memerlukan komponen berkualitas yang dapat Anda ambil dan gunakan dengan mudah. Akar masalahnya adalah komponen yang kami rencanakan untuk digunakan kembali ditulis dengan tidak benar. Sulit untuk memperluas dan menerapkannya di tempat lain.



Komponen yang dapat digunakan kembali harus cukup fleksibel dan sederhana pada saat yang bersamaan. Bekerja dengannya seharusnya tidak menimbulkan rasa sakit dan menyerupai menembak burung gereja dari meriam. Di sisi lain, komponen harus cukup dapat disesuaikan sehingga dengan sedikit perubahan pada skrip, tidak menjadi jelas bahwa lebih mudah untuk menulis "Komponen2".



SOLID Menuju Komponen yang Dapat Digunakan Kembali



Untuk menulis komponen berkualitas, kita memerlukan seperangkat aturan di balik akronim SOLID. Aturan-aturan ini menjelaskan bagaimana menggabungkan fungsi dan struktur data ke dalam kelas, dan bagaimana kelas harus digabungkan satu sama lain.



Mengapa tepatnya SOLID dan bukan seperangkat prinsip lainnya? Aturan SOLID memberi tahu Anda cara merancang aplikasi dengan benar. Sehingga Anda dapat dengan aman mengembangkan proyek, menambahkan fungsi baru, mengubah yang sudah ada dan pada saat yang sama tidak merusak segalanya. Ketika saya mencoba menjelaskan apa, menurut pendapat saya, komponen yang baik itu, saya menyadari bahwa kriteria saya mendekati prinsip-prinsip SOLID.



  • S adalah prinsip tanggung jawab tunggal.
  • O - prinsip keterbukaan / kedekatan.
  • L adalah prinsip substitusi Liskov.
  • I - prinsip pemisahan antarmuka.
  • D - Prinsip Inversi Ketergantungan.


Beberapa prinsip ini bekerja dengan baik untuk menggambarkan komponen. Yang lain terlihat lebih tidak masuk akal dalam konteks frontend. Tetapi secara keseluruhan mereka menggambarkan visi saya tentang komponen berkualitas dengan baik.



Kami akan mengikuti prinsip-prinsip di luar urutan, tetapi dari yang sederhana hingga yang kompleks. Pertama, mari kita lihat hal-hal dasar yang dapat berguna dalam banyak situasi, dan kemudian - yang lebih kuat dan spesifik.



Artikel ini memberikan contoh kode dalam React + TypeScript. Saya memilih React sebagai framework yang paling sering saya gunakan. Sebagai gantinya dapat berupa kerangka kerja lain yang Anda sukai atau sesuaikan. Alih-alih TS, mungkin ada JS murni, tetapi TypeScript memungkinkan Anda untuk secara eksplisit menggambarkan kontrak dalam kode, yang menyederhanakan pengembangan dan penggunaan komponen kompleks.



Dasar



Prinsip buka / tutup



Entitas perangkat lunak harus terbuka untuk ekstensi dan tertutup untuk modifikasi. Dengan kata lain, kita harus dapat memperluas fungsionalitas dengan kode baru tanpa mengubah yang sudah ada. Mengapa itu penting? Jika setiap kali Anda harus mengedit banyak modul yang ada untuk menambahkan fungsionalitas baru, seluruh proyek akan menjadi tidak stabil. Akan ada banyak tempat yang dapat rusak karena fakta bahwa mereka terus berubah.



Mari kita pertimbangkan penerapan prinsip pada contoh tombol. Kami telah membuat komponen tombol dan memiliki gaya. Sejauh ini, semuanya telah bekerja dengan baik. Tapi kemudian tugas baru masuk, dan ternyata di satu tempat khusus untuk tombol ini, gaya yang berbeda perlu diterapkan.





Tombol ditulis sedemikian rupa sehingga tidak dapat diubah tanpa mengedit kode



Untuk menerapkan gaya yang berbeda dalam versi saat ini, Anda harus mengedit komponen tombol. Masalahnya adalah komponen tersebut tidak dapat disesuaikan. Kami tidak akan mempertimbangkan opsi untuk menulis gaya global, karena tidak dapat diandalkan. Apa pun bisa rusak dengan pengeditan apa pun. Konsekuensinya mudah dibayangkan jika Anda meletakkan sesuatu yang lebih kompleks di tempat tombol, misalnya, komponen pemilih tanggal.



Menurut prinsip keterbukaan/ketertutupan, kita harus menulis kode sehingga ketika menambahkan gaya baru, kita tidak perlu menulis ulang kode tombol. Semuanya akan berhasil jika bagian dari gaya komponen dapat dibuang ke luar. Untuk melakukan ini, kita akan membuat prop di mana kita akan melewati kelas yang diperlukan untuk menggambarkan gaya baru komponen.



//    ,    
import cx from 'classnames';

//    — mix
const Button = ({ children, mix }) => {
  return (
    <button
      className={cx("my-button", mix)}
    >
      {children}
    </button>
}

      
      





Selesai, sekarang Anda tidak perlu mengedit kodenya untuk menyesuaikan komponen.







Metode yang agak populer ini memungkinkan Anda untuk menyesuaikan tampilan komponen. Disebut campuran karena kelas tambahan dicampur dengan kelas komponen itu sendiri. Perhatikan bahwa melewati kelas bukan satu-satunya cara untuk menata komponen dari luar. Anda dapat meneruskan objek dengan properti CSS ke komponen. Anda dapat menggunakan solusi CSS-in-JS, esensinya tidak akan berubah. Campuran digunakan oleh banyak pustaka komponen, misalnya: MaterialUI, Vuetify, PrimeNG, dan lainnya.



Kesimpulan apa yang dapat ditarik tentang campuran? Mereka mudah diterapkan, serbaguna, dan memungkinkan Anda menyesuaikan tampilan komponen secara fleksibel dengan sedikit usaha.



Tetapi pendekatan ini juga memiliki kelemahan. Ini memungkinkan banyak kebebasan, yang dapat menyebabkan masalah dengan kekhususan penyeleksi. Itu juga merusak enkapsulasi. Untuk menghasilkan pemilih css yang benar, Anda perlu mengetahui struktur internal komponen. Ini berarti bahwa kode tersebut dapat rusak saat refactoring komponen.



Variabilitas komponen



Sebuah komponen memiliki bagian-bagian yang menjadi intinya. Jika kita mengubahnya, kita mendapatkan komponen yang berbeda. Untuk sebuah tombol, itu adalah satu set status dan perilaku. Pengguna membedakan tombol dari, misalnya, kotak centang, berkat efek kursor dan kliknya. Ada logika kerja umum: ketika pengguna mengklik, pengendali acara dipicu. Ini adalah inti dari komponen, yang membuat tombol menjadi tombol. Ya, ada pengecualian, tetapi begitulah cara kerjanya di sebagian besar kasus penggunaan.



Ada juga bagian dalam komponen yang bisa berubah tergantung tempat penggunaan. Gaya termasuk dalam grup ini. Mungkin kita membutuhkan tombol dengan ukuran atau warna yang berbeda. Dengan stroke dan fillet yang berbeda, atau dengan efek hover yang berbeda. Semua gaya adalah bagian komponen yang dapat dimodifikasi. Kami tidak ingin menulis ulang atau membuat komponen baru setiap kali tombol terlihat berbeda.



Perubahan apa yang sering harus diubah tanpa mengubah kode. Jika tidak, kita akan menemukan diri kita dalam situasi di mana lebih mudah untuk membuat komponen baru daripada menyesuaikan dan menambahkan yang lama, yang ternyata tidak cukup fleksibel.



Bertema



Mari kembali menyesuaikan visual komponen menggunakan contoh tombol. Cara selanjutnya adalah dengan menerapkan tema. Dengan tema, maksud saya kemampuan komponen untuk muncul dalam beberapa mode, berbeda di tempat yang berbeda. Penafsiran ini lebih luas dari tema dalam konteks tema terang dan tema gelap.



Penggunaan tema tidak mengecualikan metode sebelumnya dengan campuran, tetapi melengkapinya. Kami secara eksplisit mengatakan bahwa suatu komponen memiliki beberapa metode tampilan dan, ketika digunakan, mengharuskan Anda untuk menentukan yang diinginkan.



import cx from 'classnames';
import b from 'b_';

const Button = ({ children, mix, theme }) => (
  <button
    className={cx(
     b("my-button", { theme }), mix)}
  >
    {children}
  </button>
)

      
      





Tema memungkinkan Anda untuk menghindari kebun binatang gaya, ketika, misalnya, Anda memiliki 20 tombol dalam proyek Anda dan semuanya terlihat sedikit berbeda karena fakta bahwa gaya setiap tombol diatur di tempat aplikasi. Pendekatan ini dapat diterapkan ke semua komponen baru tanpa takut overengineering. Jika Anda memahami bahwa suatu komponen dapat terlihat berbeda, sebaiknya tema secara eksplisit dari awal. Ini akan menyederhanakan pengembangan komponen lebih lanjut.



Tetapi ada juga kelemahannya - metode ini hanya cocok untuk menyesuaikan visual dan tidak memungkinkan untuk memengaruhi perilaku komponen.



Komponen bersarang



Saya belum membuat daftar semua cara untuk menghindari perubahan kode komponen saat menambahkan fungsi baru. Yang lain akan ditunjukkan dengan memeriksa prinsip-prinsip lainnya. Di sini saya ingin menyebutkan komponen dan slot anak.



Halaman web adalah hierarki komponen seperti pohon. Setiap komponen memutuskan sendiri apa dan bagaimana membuat. Tapi itu tidak selalu terjadi. Misalnya, sebuah tombol memungkinkan Anda menentukan konten apa yang akan dirender secara internal. Di React, alat utama adalah children prop dan render props. Vue memiliki konsep slot yang lebih kuat. Tidak ada masalah saat menulis komponen sederhana menggunakan kemampuan ini. Tetapi penting untuk tidak lupa bahwa bahkan dalam komponen yang kompleks, Anda dapat menggunakan pelemparan beberapa elemen yang seharusnya ditampilkan oleh komponen dari atas.



Maju



Prinsip-prinsip yang dijelaskan di bawah ini cocok untuk proyek yang lebih besar. Teknik yang sesuai memberikan lebih banyak fleksibilitas, tetapi meningkatkan kompleksitas desain dan pengembangan.



Prinsip Tanggung Jawab Tunggal



Prinsip tanggung jawab tunggal berarti bahwa modul harus memiliki satu dan hanya satu alasan untuk berubah.



Mengapa itu penting? Akibat dari pelanggaran asas tersebut antara lain:

  • Risiko merusak yang lain saat mengedit satu bagian dari sistem.
  • Abstraksi yang buruk. Hasilnya adalah komponen yang dapat melakukan beberapa fungsi, yang membuat sulit untuk memahami apa sebenarnya komponen yang harus dilakukan dan apa yang tidak.
  • Pekerjaan yang tidak nyaman dengan komponen. Sangat sulit untuk melakukan perbaikan atau memperbaiki bug pada komponen yang melakukan semuanya sekaligus.


Mari kembali ke contoh tema dan lihat apakah prinsip tanggung jawab tunggal dihormati. Sudah dalam bentuknya saat ini, tema mengatasi tugasnya, tetapi ini tidak berarti bahwa solusinya tidak memiliki masalah dan tidak dapat ditingkatkan.





Satu modul diedit oleh orang yang berbeda untuk alasan yang berbeda



Katakanlah kita meletakkan semua gaya dalam satu file css. Itu dapat diedit oleh orang yang berbeda untuk alasan yang berbeda. Ternyata prinsip tanggung jawab tunggal telah dilanggar. Seseorang dapat memfaktorkan ulang gaya, dan pengembang lain akan mengubah fitur baru. Jadi Anda dapat dengan mudah memecahkan sesuatu.



Mari kita lihat seperti apa tema yang sesuai dengan SRP. Gambar yang sempurna: kami memiliki tombol dan, secara terpisah, satu set tema untuk itu. Kita dapat menerapkan tema ke tombol dan mendapatkan tombol bertema. Sebagai bonus, saya ingin dapat merakit tombol dengan beberapa tema yang tersedia, misalnya, untuk ditempatkan di pustaka komponen.





Lukisan yang diinginkan. Tema adalah entitas terpisah dan dapat diterapkan ke tombol.



Tema membungkus tombol. Ini adalah pendekatan yang digunakan di Lego, perpustakaan komponen internal kami. Kami menggunakan HOC (High Order Components) untuk membungkus komponen dasar dan menambahkan fitur baru ke dalamnya. Misalnya, kemampuan untuk menampilkan dengan tema.



HOC adalah fungsi yang mengambil komponen dan mengembalikan komponen lain. HOC dengan tema dapat melempar objek dengan gaya di dalam tombol. Di bawah ini adalah opsi yang agak mendidik, dalam kehidupan nyata Anda dapat menggunakan solusi yang lebih elegan, misalnya, memasukkan kelas ke dalam komponen, gaya yang diimpor ke HOC, atau menggunakan solusi CSS-in-JS.



Contoh HOC sederhana untuk tema tombol:



const withTheme1 = (Button) =>
(props) => {
    return (
        <Button
            {...props}
            styles={theme1Styles}
        />
    )
}

const Theme1Button = withTheme1(Button);
      
      





HOC hanya dapat menerapkan gaya jika tema tertentu ditentukan, jika tidak, tidak akan melakukan apa pun. Jadi kita bisa merakit tombol dengan satu set tema dan mengaktifkan yang kita butuhkan dengan menentukan prop tema.



Menggunakan beberapa HOC untuk mengumpulkan tombol dengan tema yang diinginkan:



import "./styles.css";
 
//   .     
const ButtonBase = ({ style, children }) => {
 console.log("styl123e", style);
 return <button style={style}>{children}</button>;
};
 
const withTheme1 = (Button) => (props) => {
 // HOC  ,     "theme1"
 if (props.theme === "theme1") {
   return <Button {...props} style={{ color: "red" }} />;
 }
 
 return <Button {...props} />;
};
 
const withTheme2 = (Button) => (props) => {
 // HOC  ,     "theme2"
 if (props.theme === "theme2") {
   return <Button {...props} style={{ color: "green" }} />;
 }
 
 return <Button {...props} />;
};
 
// -      HOC
const compose = (...hocs) => (BaseComponent) =>
 hocs.reduce((Component, nextHOC) => nextHOC(Component), BaseComponent);
 
//  ,    
const Button = compose(withTheme1, withTheme2)(ButtonBase);
 
export default function App() {
 return (
   <div className="App">
     <Button theme="theme1">"Red"</Button>
     <Button theme="theme2">"Green"</Button>
   </div>
 );
}

      
      





Dan di sini kita sampai pada kesimpulan bahwa kita perlu membagi bidang tanggung jawab. Sekalipun tampaknya Anda memiliki satu komponen, pikirkan - benarkah demikian? Mungkin itu harus dibagi menjadi beberapa lapisan, yang masing-masing akan bertanggung jawab untuk fungsi tertentu. Dalam hampir semua kasus, lapisan visual dapat dipisahkan dari logika komponen.



Memisahkan tema menjadi entitas terpisah memberikan keuntungan pada kegunaan komponen: Anda dapat menempatkan tombol di perpustakaan dengan kumpulan tema dasar dan memungkinkan pengguna untuk menulis sendiri jika perlu; topik dapat dengan mudah meraba-raba antara proyek. Ini memungkinkan Anda untuk mempertahankan konsistensi antarmuka dan tidak membebani pustaka asli.



Ada opsi berbeda untuk menerapkan pembagian ke dalam lapisan. Contoh di atas adalah dengan HOC, tetapi komposisi juga dimungkinkan. Namun, saya percaya bahwa dalam hal tema, HOC lebih tepat, karena tema bukanlah komponen yang berdiri sendiri.



Bukan hanya visual yang bisa dibawa ke dalam lapisan tersendiri. Tapi saya tidak berencana untuk mempertimbangkan secara rinci transfer logika bisnis ke HOC, karena pertanyaannya sangat holistik. Pendapat saya adalah Anda dapat melakukan ini jika Anda memahami apa yang Anda lakukan dan mengapa Anda membutuhkannya.



Komponen komposit



Mari kita beralih ke komponen yang lebih kompleks. Mari kita ambil Select sebagai contoh dan lihat apa gunanya Prinsip Tanggung Jawab Tunggal. Pilih dapat dianggap sebagai komposisi komponen yang lebih kecil.







  • Wadah - komunikasi antara komponen lain.
  • Field - teks untuk pilihan biasa dan input untuk komponen CobmoBox, tempat pengguna memasukkan sesuatu.
  • Ikon - ikon tradisional di bidang untuk dipilih.
  • Menu adalah komponen yang menampilkan daftar item untuk dipilih.
  • Item adalah item terpisah dalam menu.


Untuk mematuhi prinsip tanggung jawab tunggal, Anda perlu memasukkan semua entitas ke dalam komponen terpisah, sehingga setiap orang hanya memiliki satu alasan untuk mengedit. Ketika kami memotong file, pertanyaan akan muncul: bagaimana sekarang menyesuaikan set komponen yang dihasilkan? Misalnya, jika Anda perlu mengatur tema gelap untuk bidang, perbesar ikon dan ubah warna menu. Ada dua cara untuk mencapai ini.



Mengganti



Cara pertama adalah langsung. Pindahkan semua pengaturan komponen bersarang ke alat peraga yang asli. Namun, jika Anda menerapkan solusi "langsung", ternyata pilihan memiliki sejumlah besar alat peraga, yang sulit dipahami. Hal ini diperlukan untuk mengatur mereka entah bagaimana nyaman. Di sinilah override masuk. Ini adalah konfigurasi yang diteruskan ke komponen dan memungkinkan Anda untuk menyesuaikan setiap elemennya.



<Select
  ...
  overrides={{
    Field: {
      props: {theme: 'dark'}
    },
    Icon: {
      props: {size: 'big'},
    },
    Menu: {
      style: {backgroundColor: '#CCCCCC'}
    },
  }}
/>

      
      





Saya memberikan contoh sederhana di mana kami mengganti alat peraga. Tetapi override dapat dianggap sebagai konfigurasi global - ia mengonfigurasi semua yang didukung komponen. Anda dapat melihat cara kerjanya dalam praktik di perpustakaan BaseWeb .



Secara keseluruhan, dengan menggunakan override, Anda dapat menyesuaikan komponen komposit secara fleksibel, dan pendekatan ini juga dapat diskalakan dengan baik. Kekurangan: konfigurasi untuk komponen kompleks menjadi sangat besar, dan kekuatan override memiliki kelemahan. Kami mendapatkan kontrol penuh atas komponen internal, yang memungkinkan kami melakukan hal-hal aneh dan mengekspos pengaturan yang tidak valid. Juga, jika Anda tidak menggunakan pustaka, tetapi ingin menerapkan pendekatan itu sendiri, Anda harus mengajari komponen untuk memahami konfigurasi atau menulis pembungkus yang akan membacanya dan mengonfigurasi komponen dengan benar.



Prinsip Pembalikan Ketergantungan



Untuk memahami alternatif untuk menimpa konfigurasi, mari kita beralih ke huruf D di SOLID. Ini adalah Prinsip Pembalikan Ketergantungan. Dia berpendapat bahwa kode yang mengimplementasikan kebijakan tingkat tinggi tidak boleh bergantung pada kode yang mengimplementasikan detail tingkat rendah.



Mari kita kembali ke pilihan kita. Kontainer bertanggung jawab untuk komunikasi antara bagian lain dari komponen. Bahkan, itu adalah root yang mengontrol rendering blok lainnya. Untuk melakukan ini, dia harus mengimpornya.



Beginilah tampilan akar dari setiap komponen kompleks, jika Anda tidak menggunakan inversi ketergantungan:



import InputField from './InputField';
import Icon from './Icon';
import Menu from './Menu';
import Option from './Option';
      
      





Mari kita menganalisis ketergantungan antar komponen untuk memahami apa yang bisa salah. Sekarang Pilih tingkat yang lebih tinggi tergantung pada Menu tingkat yang lebih rendah, karena akan mengimpornya ke dalam dirinya sendiri. Prinsip inversi ketergantungan rusak. Ini menciptakan masalah.

  • Pertama, jika Anda mengubah Menu, Anda harus mengedit Select.
  • Kedua, jika kita ingin menggunakan komponen menu yang berbeda, kita juga harus mengedit komponen pilih.




Tidak jelas apa yang harus dilakukan ketika Anda perlu Pilih dengan menu yang berbeda



Anda perlu memperluas ketergantungan. Jadikan komponen menu tergantung pada pilih. Pembalikan ketergantungan dilakukan melalui injeksi ketergantungan - Pilih harus menerima komponen menu sebagai salah satu parameter, props. Di sinilah mengetik berguna. Kami akan menunjukkan komponen mana yang diharapkan oleh Select.



//     Select      
const Select = ({
  Menu: React.ComponentType<IMenu>
}) => {
  return (
    ...
    <Menu>
      ...
    </Menu>
    ...
  )
...
}

      
      





Ini adalah bagaimana kami mendeklarasikan bahwa pilih membutuhkan komponen menu yang propsnya memenuhi antarmuka tertentu. Kemudian panah akan menunjuk ke arah yang berlawanan, seperti yang ditentukan oleh prinsip DI.





Panah diperluas, ini adalah cara kerja Pembalikan Ketergantungan



Kami telah memecahkan masalah ketergantungan, tetapi sedikit gula sintaksis dan alat pembantu diterima di sini.



Setiap kali, membuang semua dependensi ke dalam komponen di lokasi render itu membosankan, tetapi perpustakaan bem-react memiliki registri dependensi dan proses komposisi. Dengan bantuan mereka, Anda dapat mengemas dependensi dan pengaturan sekali, dan kemudian hanya menggunakan komponen yang sudah jadi.



import { compose } from '@bem-react/core'
import { withRegistry, Registry } from '@bem-react/di'

const selectRegistry = new Registry({ id: cnSelect() })

...

selectRegistry.fill({
    'Trigger': Button,
    'Popup': Popup,
    'Menu': Menu,
    'Icon': Icon,
})

const Select = compose(
    ...
    withRegistry(selectRegistry),
)(SelectDesktop)
      
      





Contoh di atas menunjukkan bagian dari perakitan komponen menggunakan contoh bem-react. Contoh lengkap kode dan sandbox dapat ditemukan di buku cerita yandex UI .



Apa yang kita dapatkan dari menggunakan Dependency Inversion?



  • Kontrol penuh - kebebasan untuk menyesuaikan semua komponen komponen.
  • Enkapsulasi fleksibel - kemampuan untuk membuat komponen menjadi sangat fleksibel dan dapat disesuaikan sepenuhnya. Jika perlu, pengembang akan menimpa semua blok yang membentuk komponen dan mendapatkan apa yang diinginkannya. Dalam hal ini, selalu ada opsi untuk membuat komponen yang sudah dikonfigurasi dan siap pakai.
  • Skalabilitas - Metode ini bekerja dengan baik untuk perpustakaan dari berbagai ukuran.


Kami di Yandex.Tutorial menulis komponen kami sendiri menggunakan DI. Pustaka komponen Lego internal juga menggunakan pendekatan ini. Tetapi ia memiliki satu kelemahan signifikan - perkembangan yang jauh lebih kompleks.



Kesulitan dalam mengembangkan komponen yang dapat digunakan kembali



Apa kesulitan dalam mengembangkan komponen yang dapat digunakan kembali?



Pertama, desain yang panjang dan hati-hati. Anda perlu memahami bagian mana yang terbuat dari komponen dan bagian apa yang dapat diubah. Jika kita membuat semua bagian bisa berubah, kita berakhir dengan sejumlah besar abstraksi yang sulit dimengerti. Jika ada terlalu sedikit bagian yang dapat diganti, komponen tersebut tidak akan cukup fleksibel. Ini perlu ditingkatkan untuk menghindari masalah penggunaan kembali di masa mendatang.



Kedua, persyaratan tinggi untuk komponen. Anda memahami bagian apa yang akan terdiri dari komponen. Sekarang Anda perlu menulisnya sehingga mereka tidak tahu apa-apa tentang satu sama lain, tetapi dapat digunakan bersama. Ini lebih sulit daripada mengembangkan tanpa memperhatikan dapat digunakan kembali.



Ketiga, struktur yang kompleks sebagai konsekuensi dari poin-poin sebelumnya. Jika Anda memerlukan penyesuaian yang serius, Anda harus membangun kembali semua dependensi komponen. Untuk melakukan ini, Anda perlu memahami secara mendalam bagian-bagian apa yang terdiri darinya. Dokumentasi yang baik sangat penting dalam prosesnya.



Tutorial memiliki perpustakaan komponen internal di mana mekanik pendidikan berada - bagian dari antarmuka yang berinteraksi dengan anak-anak saat memecahkan masalah. Dan kemudian ada perpustakaan bersama layanan pendidikan. Kami menempatkan komponen di sana yang ingin kami gunakan kembali di antara layanan yang berbeda.



Pengalihan satu mekanik memakan waktu beberapa minggu, asalkan kami sudah memiliki komponen yang berfungsi dan kami tidak menambahkan fungsionalitas baru. Sebagian besar pekerjaan ini adalah untuk melihat komponen menjadi potongan independen dan memungkinkan mereka untuk dibagikan.



Prinsip Substitusi Liskov



Prinsip sebelumnya adalah tentang apa yang harus dilakukan, dan dua prinsip terakhir tentang apa yang tidak boleh dilanggar.



Mari kita mulai dengan prinsip substitusi Barbara Liskov. Dia mengatakan bahwa objek dalam suatu program harus dapat diganti dengan turunan dari subtipenya tanpa merusak eksekusi program yang benar.



Kami biasanya tidak menulis komponen sebagai kelas dan kami tidak menggunakan pewarisan. Semua komponen dapat dipertukarkan di luar kotak. Ini adalah dasar dari frontend modern. Pengetikan yang kuat membantu Anda menghindari kesalahan dan menjaga kompatibilitas.



Bagaimana kemampuan penggantian yang tidak biasa dapat rusak? Komponen memiliki API. Dengan API, maksud saya satu set alat peraga untuk komponen dan mekanisme yang dibangun ke dalam kerangka kerja, seperti mekanisme pengendali acara. Pengetikan dan linting yang kuat di IDE dapat menyoroti ketidakcocokan di API, tetapi komponen dapat berinteraksi dengan dunia luar dan mengabaikan API:



  • membaca dan menulis sesuatu ke toko global,
  • berinteraksi dengan jendela,
  • berinteraksi dengan cookie,
  • baca / tulis penyimpanan lokal,
  • membuat permintaan ke jaringan.






Semua ini tidak aman, karena komponen tergantung pada lingkungan dan dapat rusak jika Anda memindahkannya ke tempat lain atau ke proyek lain.



Untuk mematuhi prinsip substitusi Liskov, Anda perlu:



  • menggunakan kemampuan mengetik,
  • hindari interaksi yang melewati API komponen,
  • menghindari efek samping.


Bagaimana cara menghindari interaksi non-API? Letakkan semua komponen yang bergantung pada API dan tulis pembungkus yang akan meneruskan data dari dunia luar ke props. Misalnya, seperti ini:

const Component = () => {
   /*
       ,     ,       .
      ,          .
          ,   ,   .
   */
   const {userName} = useStore();
 
   //     ,       ( ,       ).
   const userToken = getFromCookie();
 
   //  —   window      .
   const {taskList} = window.ssrData;
 
   const handleTaskUpdate = () => {
       //    API .      .
       fetch(...)
   }
 
   return <div>{'...'}</div>;
  };
 
/*
          .
      ,         .
*/
const Component2 = ({
   userName, userToken, onTaskUpdate
}) => {
   return <div>{'...'}</div>;
};

      
      





Prinsip Segregasi Antarmuka



Banyak antarmuka tujuan khusus lebih baik daripada satu antarmuka tujuan umum. Saya tidak dapat mentransfer prinsip ke komponen front-end secara jelas. Jadi saya memahaminya sebagai kebutuhan untuk mengawasi API.



Penting untuk mentransfer sesedikit mungkin entitas ke komponen dan tidak mentransfer data yang tidak digunakan olehnya. Banyaknya props dalam sebuah komponen menjadi alasan untuk waspada. Kemungkinan besar, itu melanggar prinsip SOLID.



Di mana dan bagaimana kita menggunakan kembali?



Kami telah membahas prinsip untuk membantu Anda menulis komponen berkualitas. Sekarang mari kita lihat di mana dan bagaimana kita akan menggunakannya kembali. Ini akan membantu Anda memahami masalah lain yang mungkin Anda hadapi.



Konteksnya bisa berbeda: Anda perlu menggunakan komponen di tempat lain pada halaman yang sama, atau, misalnya, Anda ingin menggunakannya kembali di proyek perusahaan lain - ini adalah hal yang sama sekali berbeda. Saya menyoroti beberapa opsi:



Belum diperlukan penggunaan kembali.Anda telah menulis sebuah komponen, Anda berpikir bahwa itu spesifik dan tidak berencana untuk menggunakannya di tempat lain. Tidak perlu melakukan upaya tambahan. Dan Anda dapat melakukan beberapa langkah sederhana yang akan berguna jika Anda masih ingin kembali ke sana. Jadi, misalnya, Anda dapat memeriksa bahwa komponen tidak terlalu terikat dengan lingkungan, dan dependensinya dibungkus. Anda juga dapat membuat cadangan untuk penyesuaian untuk masa mendatang: menambahkan tema atau kemampuan untuk mengubah tampilan komponen dari luar (seperti pada contoh dengan tombol) - tidak memakan banyak waktu.



Gunakan kembali dalam proyek yang sama.Anda telah menulis sebuah komponen dan cukup yakin Anda ingin menggunakannya kembali di tempat lain dalam proyek Anda saat ini. Semua yang tertulis di atas relevan di sini. Hanya sekarang sangat penting untuk menghapus semua dependensi dalam pembungkus eksternal dan sangat diinginkan untuk dapat menyesuaikan dari luar (tema atau campuran). Jika suatu komponen mengandung banyak logika, Anda harus memikirkan apakah itu diperlukan di mana-mana, atau harus dimodifikasi di beberapa tempat. Untuk opsi kedua, pertimbangkan kemungkinan penyesuaian. Penting juga di sini untuk memikirkan struktur komponen dan memecahnya menjadi beberapa bagian jika perlu.



Gunakan kembali pada tumpukan serupa.Anda memahami bahwa komponen akan berguna dalam proyek tetangga yang memiliki tumpukan yang sama dengan Anda. Di sinilah semua hal di atas menjadi wajib. Selain itu, saya menyarankan Anda untuk memantau dependensi dan teknologi dengan cermat. Apakah proyek tetangga persis menggunakan versi perpustakaan yang sama seperti Anda? Apakah SASS dan TypeScript menggunakan versi yang sama?



Saya juga ingin menyoroti penggunaan kembali di lingkungan runtime lain , misalnya, di SSR. Putuskan apakah komponen Anda dapat dan harus dapat dirender di SSR. Jika demikian, pastikan itu merender seperti yang diharapkan sebelumnya. Ingat bahwa ada runtime lain seperti deno atau GraalVM. Pertimbangkan fitur-fiturnya jika Anda menggunakannya.



Pustaka Komponen



Jika komponen perlu digunakan kembali antara beberapa repositori dan / atau proyek, mereka harus dipindahkan ke perpustakaan.



Tumpukan



Semakin banyak teknologi yang digunakan dalam proyek, semakin sulit untuk memecahkan masalah kompatibilitas. Yang terbaik adalah mengurangi kebun binatang dan meminimalkan jumlah teknologi yang digunakan: kerangka kerja, bahasa, versi perpustakaan besar. Jika Anda memahami bahwa Anda benar-benar membutuhkan banyak teknologi, Anda harus belajar untuk hidup dengannya. Misalnya, Anda dapat menggunakan pembungkus di atas komponen web, mengumpulkan semuanya dalam JS murni, atau menggunakan adaptor untuk komponen.



Ukuran



Jika menggunakan komponen sederhana dari perpustakaan Anda menambahkan beberapa megabita ke bundel, itu tidak masalah. Komponen seperti itu tidak ingin digunakan kembali, karena prospek menulis versi ringan Anda sendiri dari awal tampaknya dibenarkan. Anda dapat memecahkan masalah menggunakan alat kontrol ukuran, misalnya, batas ukuran.



Jangan lupa tentang modularitas - pengembang yang ingin menggunakan komponen Anda harus dapat mengambilnya saja, dan tidak menyeret semua kode pustaka ke dalam proyek.



Adalah penting bahwa perpustakaan modular tidak dikompilasi menjadi satu file. Anda juga perlu melacak versi JS yang akan digunakan oleh perpustakaan. Jika Anda membangun perpustakaan di ES.NEXT, dan proyek di ES5, akan ada masalah. Anda juga perlu mengonfigurasi perakitan dengan benar untuk versi browser yang lebih lama dan memastikan bahwa semua pengguna perpustakaan tahu apa yang akan terjadi. Jika ini terlalu rumit, ada alternatif - untuk mengatur aturan bangunan perpustakaan Anda sendiri di setiap proyek.



Memperbarui



Pikirkan terlebih dahulu tentang bagaimana Anda akan memperbarui perpustakaan. Ada baiknya jika Anda mengetahui semua klien dan skrip khusus mereka. Ini akan membantu Anda berpikir lebih baik tentang migrasi dan melanggar perubahan. Misalnya, tim yang menggunakan perpustakaan Anda akan merasa sangat frustasi untuk mempelajari tentang pembaruan besar dengan melanggar perubahan sebelum rilis.



Dengan memindahkan komponen ke pustaka yang digunakan orang lain, Anda kehilangan kemudahan pemfaktoran ulang. Untuk mencegah beban refactoring menjadi berlebihan, saya menyarankan Anda untuk tidak menyeret komponen baru ke perpustakaan. Mereka cenderung berubah, yang berarti Anda harus menghabiskan banyak waktu untuk memperbarui dan mempertahankan kompatibilitas.



Kustomisasi dan desain



Desain tidak memengaruhi kegunaan ulang, tetapi merupakan bagian penting dari penyesuaian. Dalam Tutorial kami, komponen tidak hidup sendiri, penampilannya dirancang oleh desainer. Desainer memiliki sistem desain. Jika komponen terlihat berbeda dalam sistem dan repositori, masalah tidak dapat dihindari. Desainer dan pengembang tidak memiliki ide yang sama tentang tampilan antarmuka, yang dapat menyebabkan keputusan yang buruk.



Pameran komponen



Pameran komponen akan membantu menyederhanakan interaksi dengan desainer. Salah satu solusi showcase yang populer adalah Storybook . Dengan menggunakan alat ini atau alat lain yang sesuai, Anda dapat menunjukkan komponen proyek kepada siapa pun di luar pengembangan.



Tambahkan interaktivitas ke etalase - desainer harus dapat berinteraksi dengan komponen dan melihat bagaimana mereka ditampilkan dan bekerja dengan parameter yang berbeda.



Jangan lupa untuk mengonfigurasi etalase untuk memperbarui secara otomatis saat Anda memperbarui komponen. Untuk melakukan ini, Anda perlu memindahkan proses ke CI. Sekarang desainer selalu dapat melihat komponen siap pakai apa yang ada dalam proyek dan menggunakannya.







Sistem desain



Untuk pengembang, sistem desain adalah seperangkat aturan yang mengatur penampilan komponen dalam sebuah proyek. Agar kebun binatang komponen tidak bertambah, Anda dapat membatasi kemampuan penyesuaian hingga batasnya.



Poin penting lainnya adalah sistem desain dan jenis komponen dalam proyek terkadang berbeda satu sama lain. Misalnya ketika ada redesign besar-besaran dan tidak semuanya bisa di-update di code, atau Anda harus menyesuaikan komponen, tetapi tidak ada waktu untuk melakukan perubahan pada sistem desain. Dalam kasus ini, baik kepentingan Anda dan kepentingan desainer untuk menyinkronkan sistem desain dan proyek segera setelah ada kesempatan.



Nasihat universal dan jelas terakhir: berkomunikasi dan bernegosiasi. Tidak perlu menganggap desainer sebagai mereka yang berdiri di samping pengembangan dan hanya membuat dan mengedit tata letak. Bekerja sama dengan mereka akan membantu Anda merancang dan mengimplementasikan antarmuka yang berkualitas. Pada akhirnya, ini akan menguntungkan tujuan bersama dan menyenangkan pengguna produk.



kesimpulan



Kode duplikat menyebabkan kesulitan dalam pengembangan dan penurunan kualitas frontend. Untuk menghindari konsekuensinya, Anda perlu memantau kualitas komponen, dan prinsip-prinsip SOLID membantu menulis komponen berkualitas.



Jauh lebih sulit untuk menulis komponen yang bagus dengan cadangan untuk masa depan daripada komponen yang dengan cepat menyelesaikan masalah di sini dan sekarang. Pada saat yang sama, "batu bata" yang baik hanyalah bagian dari solusi. Jika Anda membawa komponen ke perpustakaan, Anda perlu membuatnya nyaman, dan mereka juga perlu disinkronkan dengan sistem desain.



Seperti yang Anda lihat, tugasnya tidak mudah. Sulit dan memakan waktu untuk mengembangkan komponen berkualitas tinggi yang dapat digunakan kembali. Apakah itu layak? Saya percaya bahwa setiap orang akan menjawab pertanyaan ini untuk dirinya sendiri. Untuk proyek kecil, overhead bisa terlalu tinggi. Untuk proyek di mana pengembangan jangka panjang tidak direncanakan, upaya investasi dalam penggunaan kembali kode juga merupakan keputusan yang kontroversial. Namun, setelah mengatakan "kami tidak membutuhkan ini sekarang," mudah untuk mengabaikan bagaimana Anda berakhir dalam situasi di mana kurangnya komponen yang dapat digunakan kembali akan membawa banyak masalah yang mungkin tidak terjadi. Jadi jangan ulangi kesalahan kami dan jangan ulangi dirimu sendiri!



Tonton laporannya



All Articles