Untuk membuat antarmuka, Bereaksi merekomendasikan menggunakan perpustakaan komposisi dan manajemen negara untuk membangun hierarki komponen. Namun, dengan pola komposisi yang kompleks, masalah muncul:
- Perlu tidak perlu struktur elemen anak
- Atau lulus sebagai alat peraga, yang mempersulit keterbacaan, semantik dan struktur kode
Bagi sebagian besar pengembang, masalahnya mungkin tidak jelas, dan mereka membuangnya ke tingkat manajemen negara. Ini juga dibahas dalam dokumentasi Bereaksi:
Jika Anda ingin membuang beberapa alat peraga ke berbagai tingkatan, biasanya komposisi komponen adalah solusi yang lebih sederhana daripada konteks.
Bereaksi dokumentasi Konteks.
Jika kita mengikuti tautan, kita akan melihat argumen lain:
Di Facebook, kami menggunakan Bereaksi dalam ribuan komponen, dan kami belum menemukan kasus di mana kami akan merekomendasikan membuat hierarki warisan komponen.
Bereaksi dokumentasi Komposisi versus warisan.
Tentu saja, jika setiap orang yang menggunakan alat membaca dokumentasi dan mengenali otoritas penulis, publikasi ini tidak akan. Oleh karena itu, kami menganalisis masalah dari pendekatan yang ada.
Pola # 1 - Komponen yang Terkendali Langsung

Saya mulai dengan solusi ini karena kerangka kerja Vue yang merekomendasikan pendekatan ini. Kami mengambil struktur data yang berasal dari dukungan atau desain dalam hal formulir. Teruskan ke komponen kami - misalnya, ke kartu film:
const MovieCard = (props) => { const {title, genre, description, rating, image} = props.data; return ( <div> <img href={image} /> <div><h2>{title}</h2></div> <div><p>{genre}</p></div> <div><p>{description}</p></div> <div><h1>{rating}</h1></div> </div> ) }
Berhenti Kita sudah tahu tentang perluasan kebutuhan komponen yang tiada akhir. Tiba-tiba judul akan memiliki tautan ke ulasan film? Dan genre - untuk film terbaik darinya? Jangan tambahkan sekarang:
const MovieCard = (props) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> <div><h1>{rating}</h1></div> <div><p>{description}</p></div> </div> ) }
Jadi kita akan melindungi diri kita dari masalah di masa depan, tetapi membuka pintu ke kesalahan titik nol. Awalnya, kami bisa melempar struktur langsung dari belakang:
<MovieCard data={res.data} />
Sekarang setiap kali Anda perlu menggandakan semua informasi:
<MovieCard data={{ title: {res.title}, description: {res.description}, rating: {res.rating}, image: {res.imageHref} }} />
Namun, kami lupa tentang genre - dan komponen jatuh. Dan jika pembatas kesalahan tidak disetel, maka seluruh aplikasi menyertainya.
TypeScript datang untuk menyelamatkan. Kami menyederhanakan skema dengan refactoring kartu dan elemen yang menggunakannya. Untungnya, semuanya disorot dalam editor atau selama pertemuan:
interface IMovieCardElement { text?: string; } interface IMovieCardImage { imageHref?: string; } interface IMovieCardProps { title: IMovieCardElement; description: IMovieCardElement; rating: IMovieCardElement; genre: IMovieCardElement; image: IMovieCardImage; } ... const {title: {text: title}, description: {text: description}, rating: {text: rating}, genre: {text: genre}, image: {imageHref} } = props.data;
Untuk menghemat waktu, kami masih akan menggulung data "seperti apa pun" atau "sebagai IMovieCardProps". Apa yang terjadi? Kami telah menggambarkan tiga kali (jika digunakan di satu tempat) satu struktur data. Dan apa yang kita miliki? Komponen yang masih tidak dapat dimodifikasi. Komponen yang berpotensi menyebabkan crash seluruh aplikasi.
Sudah waktunya untuk menggunakan kembali komponen ini. Peringkat tidak lagi dibutuhkan. Kami memiliki dua opsi:
Pasang prop tanpa peringkat di mana pun peringkat dibutuhkan
const MovieCard = ({withoutRating, ...props}) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { withoutRating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) }
Cepat, tetapi kami menumpuk alat peraga dan membangun struktur data keempat.
Membuat peringkat di IMovieCardProps opsional. Jangan lupa untuk menjadikannya objek kosong secara default
const MovieCard = ({data, ...props}) => { const {title: {text: title}, description: {text: description}, rating: {text: rating} = {}, genre: {text: genre}, image: {imageHref} } = data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { data.rating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) }
Lebih rumit, tetapi menjadi sulit untuk membaca kodenya. Sekali lagi, ulangi untuk keempat kalinya . Kontrol atas komponen tidak jelas, karena sangat dikontrol oleh struktur data. Katakanlah kita diminta membuat peringkat yang terkenal sebagai tautan, tetapi tidak di mana-mana:
rating: {text: rating, url: ratingUrl} = {}, ... { data.rating && data.rating.url ? <div>><h1><a href={ratingUrl}{rating}</a></h1></div> : <div><h1>{rating}</h1></div> }
Dan di sini kita mengalami logika kompleks yang menentukan struktur data buram.
Pola No. 2 - Komponen dengan kondisi dan reduksi sendiri

Pada saat yang sama, pendekatan yang aneh dan populer. Saya menggunakannya ketika saya mulai bekerja dengan Bereaksi dan fungsi JSX di Vue kurang. Lebih dari sekali saya mendengar dari pengembang di mitaps bahwa pendekatan ini memungkinkan Anda untuk melewati struktur data yang lebih umum:
- Komponen dapat mengambil banyak struktur data, tata letak tetap sama
- Saat menerima data, itu memprosesnya sesuai dengan skenario yang diinginkan.
- Data disimpan dalam status komponen agar tidak memulai peredam dengan setiap render (opsional)
Secara alami, masalah opacity (1) dilengkapi dengan masalah logika berlebih (2) dan penambahan status ke komponen akhir (3).
Yang terakhir (3) ditentukan oleh keamanan internal objek. Artinya, kami sangat memeriksa objek melalui lodash.isEqual. Jika acara ini maju atau JSON.stringify, semuanya baru saja dimulai. Anda juga dapat menambahkan cap waktu dan memeriksanya jika semuanya hilang. Tidak perlu menyimpan atau memo, karena pengoptimalan bisa lebih rumit daripada reducer karena rumitnya perhitungan.
Data dilemparkan dengan nama skrip (biasanya string):
<MovieCard data={{ type: 'withoutRating', data: res.data, }} />
Sekarang tulis komponen:
const MovieCard = ({data}) => { const card = reduceData(data.type, data.data); return ( <div> <img href={card.imageHref} /> <div><h2>{card.name}</h2></div> <div><p>{card.genre}</p></div> { card.withoutRating && <div><h1>{card.rating}</h1></div> } <div><p>{card.description}</p></div> </div> ) }
Dan logikanya:
const reduceData = (type, data) = { switch (type) { case 'withoutRating': return { title: {data.title}, description: {data.description}, rating: {data.rating}, genre: {data.genre}, image: {data.imageHref} withoutRating: true, }; ... } };
Ada beberapa masalah dalam langkah ini:
- Dengan menambahkan lapisan logika, kami akhirnya kehilangan koneksi langsung antara data dan tampilan
- Duplikasi logika untuk setiap kasus berarti bahwa dalam kasus ketika semua kartu memerlukan peringkat usia, kartu tersebut harus terdaftar di setiap peredam
- Sisa Masalah Lain dari Langkah # 1
Pola # 3 - Mentransfer Logika Layar dan Data ke Manajemen Negara

Di sini kita meninggalkan bus data untuk membangun antarmuka, yaitu Bereaksi. Kami menggunakan teknologi dengan model logika kami sendiri. Ini mungkin cara paling umum untuk membangun aplikasi di Bereaksi, meskipun panduan ini memperingatkan Anda untuk tidak menggunakan konteks dengan cara ini.
Gunakan alat serupa di mana Bereaksi tidak menyediakan alat yang memadai - misalnya, dalam perutean. Kemungkinan besar Anda menggunakan router reaksi. Dalam hal ini, menggunakan konteks alih-alih meneruskan callback dari komponen setiap rute tingkat atas akan lebih masuk akal untuk meneruskan sesi ke semua halaman. Bereaksi tidak memiliki abstraksi terpisah untuk tindakan asinkron selain yang ditawarkan oleh bahasa Javascript.
Tampaknya ada nilai tambah: kita dapat menggunakan kembali logika dalam versi aplikasi yang akan datang. Tapi ini hanya tipuan. Di satu sisi, ia terikat ke API, di sisi lain, ke struktur aplikasi, dan logika menyediakan koneksi ini. Ketika mengubah salah satu bagian, itu perlu ditulis ulang.
Solusi: Pola No. 4 - komposisi

Metode komposisi jelas jika Anda mengikuti prinsip-prinsip berikut (terlepas dari pendekatan serupa dalam Pola Desain ):
- Pengembangan frontend - pengembangan antarmuka pengguna - menggunakan HTML untuk tata letak
- Javascript digunakan untuk menerima, mengirim dan memproses data.
Karena itu, transfer data dari satu domain ke domain lain sedini mungkin. Bereaksi menggunakan abstraksi JSX untuk template HTML, tetapi sebenarnya menggunakan seperangkat metode createElement. Yaitu, komponen JSX dan Bereaksi, yang juga merupakan elemen JSX, harus diperlakukan sebagai metode tampilan dan perilaku, daripada transformasi dan pemrosesan data yang harus terjadi pada tingkat yang terpisah.
Pada langkah ini, banyak menggunakan metode yang tercantum di atas, tetapi mereka tidak menyelesaikan masalah utama memperluas dan memodifikasi tampilan komponen. Cara melakukan ini, menurut pembuat perpustakaan, ditunjukkan dalam dokumentasi:
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
Artinya, sebagai parameter, alih-alih string, angka, dan tipe Boolean, komponen yang sudah jadi dan dibuat-buat ditransfer.
Sayangnya, metode ini juga ternyata tidak fleksibel. Inilah alasannya:
- Kedua alat peraga dibutuhkan. Ini membatasi penggunaan kembali komponen
- Opsional berarti kelebihan komponen SplitPane dengan logika.
- Sarang dan pluralitas tidak ditampilkan secara semantik.
- Logika pemetaan ini harus ditulis ulang untuk setiap komponen yang menerima alat peraga.
Akibatnya, solusi semacam itu dapat tumbuh dalam kompleksitas bahkan untuk skenario yang cukup sederhana:
function SplitPane(props) { return ( <div className="SplitPane"> { props.left && <div className="SplitPane-left"> {props.left} </div> } { props.right && <div className="SplitPane-right"> {props.right} </div> } </div> ); } function App() { return ( <SplitPane left={ contacts.map(el => <Contacts name={ <ContactsName name={el.name} /> } phone={ <ContactsPhone name={el.phone} /> } /> ) } right={ <Chat /> } /> ); }
Dalam dokumentasi, kode yang serupa, dalam hal komponen orde tinggi (HOC) dan render props, disebut " wrapper hell". Dengan setiap penambahan elemen baru, keterbacaan kode menjadi lebih sulit.
Satu jawaban untuk masalah ini - slot - hadir dalam teknologi komponen Web dan dalam kerangka kerja Vue . Namun di kedua tempat, ada batasan: pertama, slot didefinisikan bukan oleh simbol, tetapi oleh string, yang mempersulit refactoring. Kedua, slot terbatas fungsinya dan tidak dapat mengontrol tampilan mereka sendiri, mentransfer slot lain ke komponen anak, atau digunakan kembali dalam elemen lain.
Singkatnya, sesuatu seperti ini, sebut saja pola No. 5 - slot :
function App() { return ( <SplitPane> <LeftPane> <Contacts /> </LeftPane> <RightPane> <Chat /> </RightPane> </SplitPane> ); }
Saya akan membicarakan ini di artikel berikutnya tentang solusi yang ada untuk pola slot di Bereaksi dan tentang solusi saya sendiri.