Responsif atau responsif? Parsing React Component Structure



Pada artikel ini, kita akan memahami kompleksitas penulisan komponen adaptif, berbicara tentang pemecahan kode, mempertimbangkan beberapa cara untuk mengatur struktur kode, mengevaluasi kelebihan dan kekurangannya, dan mencoba memilih yang terbaik (tetapi ini tidak akurat).

Pertama, mari kita berurusan dengan terminologi. Kita sering mendengar istilah adaptif dan responsif . Apa yang mereka maksud? Apa bedanya? Bagaimana ini berhubungan dengan komponen kami?

Adaptive (adaptif) adalah kompleks antarmuka visual yang dibuat untuk ukuran layar tertentu. Responsif (Responsif) adalah antarmuka tunggal yang beradaptasi dengan ukuran layar apa pun.

Selain itu, ketika antarmuka didekomposisi menjadi fragmen kecil, perbedaan antara adaptif dan responsif menjadi semakin kabur, hingga menghilang sepenuhnya.

Saat mengembangkan tata letak, desainer kami, serta pengembang, paling sering tidak membagikan konsep-konsep ini dan menggabungkan logika adaptif dan responsif.

Selanjutnya, saya akan menyebut komponen yang mengandung logika adaptif dan responsif sebagai hanya adaptif . Pertama, karena saya suka kata ini lebih dari "responsif" atau, maafkan saya, "responsif". Dan kedua, saya menemukannya lebih umum.

Saya akan fokus pada dua area tampilan antarmuka - ponsel dan desktop. Yang kami maksud dengan tampilan seluler adalah lebar, misalnya, ≤ 991 piksel(angka itu sendiri tidak penting, itu hanya konstanta, yang tergantung pada sistem desain dan aplikasi Anda), dan di bawah tampilan desktop - lebarnya lebih besar dari ambang yang dipilih. Saya sengaja akan melewatkan display untuk tablet dan monitor layar lebar, karena, pertama, tidak semua orang membutuhkannya, dan kedua, akan lebih mudah untuk membuatnya seperti ini. Tetapi pola yang akan kita bahas tentang ekspansi sama untuk sejumlah "pemetaan".

Juga, saya hampir tidak akan berbicara tentang CSS , terutama kita akan berbicara tentang komponen logika.

Frontend @youla


Saya akan berbicara singkat tentang tumpukan kami di Yulia sehingga jelas dalam kondisi apa kami membuat komponen kami. Kami menggunakan React / Redux , kami bekerja di monorep, kami menggunakan TypeScript dan kami menulis CSS pada komponen-style . Sebagai contoh, mari kita lihat tiga paket kami (paket dalam konsep monoreps adalah paket NPM yang saling berhubungan, yang dapat berupa aplikasi, pustaka, utilitas atau komponen yang terpisah - Anda dapat memilih sendiri tingkat dekomposisi). Kami akan melihat dua aplikasi dan satu perpustakaan UI.

@ youla / ui- perpustakaan komponen. Mereka digunakan tidak hanya oleh kami, tetapi juga oleh tim lain yang membutuhkan antarmuka "Yulian". Pustaka memiliki banyak hal, dimulai dengan tombol dan bidang input, dan berakhir, misalnya, dengan header atau formulir otorisasi (lebih tepatnya, bagian UI-nya). Kami menganggap perpustakaan ini sebagai ketergantungan eksternal dari aplikasi kami.

@ youla-web / aplikasi-diklasifikasikan - aplikasi yang bertanggung jawab untuk bagian katalog / produk / otorisasi. Menurut persyaratan bisnis, semua antarmuka di sini harus adaptif .

@ youla-web / app-b2b adalah aplikasi yang bertanggung jawab untuk bagian dari akun pribadi Anda untuk pengguna profesional. Antarmuka aplikasi ini adalah desktop khusus .

Selanjutnya kami akan mempertimbangkan untuk menulis komponen adaptif menggunakan contoh dari paket ini. Tetapi pertama-tama Anda harus berurusan dengan isMobile.

Definisi Mobilitas isMobile && <Komponen />


import React from 'react'

const App = (props) => {
 const { isMobile } = props

 return (
   <Layout>
     {isMobile && <HeaderMobile />}
     <Content />
     <Footer />
   </Layout>
 )
}

Sebelum Anda mulai menulis komponen adaptif, Anda perlu belajar cara mendefinisikan "mobilitas". Ada banyak cara untuk mengimplementasikan definisi mobilitas. Saya ingin memikirkan beberapa poin penting.

Menentukan mobilitas berdasarkan lebar layar dan agen pengguna


Sebagian besar dari Anda tahu benar bagaimana menerapkan kedua opsi tersebut, tetapi mari kita secara singkat membahas poin-poin utama lagi.

Saat bekerja dengan lebar layar, biasanya menetapkan titik batas, setelah itu aplikasi harus berperilaku sebagai ponsel atau desktop. Prosedurnya adalah sebagai berikut:

  1. Buat konstanta dengan titik batas dan simpan di subjek (jika solusi CSS Anda memungkinkan). Nilai-nilai itu sendiri mungkin merupakan apa yang menurut desainer Anda paling tepat untuk sistem UI Anda .
  2. Kami menyimpan ukuran layar saat ini di redux / mobx / context / sumber data apa pun . Di mana saja, jika hanya komponen dan, lebih disukai, logika aplikasi memiliki akses ke data ini.
  3. Kami berlangganan ke acara mengubah ukuran dan memperbarui nilai lebar layar ke salah satu yang akan memicu rantai pembaruan pohon komponen.
  4. Kami membuat fungsi pembantu sederhana yang, menggunakan lebar dan konstanta layar, menghitung keadaan saat ini ( isMobile,isDesktop ).

Berikut adalah kode semu yang mengimplementasikan model kerja ini:

const breakpoints = {
 mobile: 991
}

export const state = {
 ui: {
   width: null
 }
}

const handleSubscribe = () => {
 state.ui.width = window.innerWidth
}

export const onSubscribe = () => {
 window.addEventListener('resize', handleSubscribe)
}

export const offSubscribe = () =>
 window.removeEventListener('resize', handleSubscribe)

export const getIsMobile = (state: any) => {
 if (state.ui.width <= breakpoints.mobile) {
   return true
 }

 return false
}

export const getIsDesktop = (state) => !getIsMobile(state)

export const App = () => {
 React.useEffect(() => {
   onSubscribe()

   return () => offSubscribe()
 }, [])

 return <MyComponentMounted />
}

const MyComponent = (props) => {
 const { isMobile } = props

 return isMobile ? <MobileComponent /> : <DesktopComponent />
}

export const MyComponentMounted = anyHocToConnectComponentWithState(
 (state) => ({
   isMobile: getIsMobile(state)
 })
)(MyComponent)

Ketika layar berubah, nilai-nilai propsuntuk komponen akan diperbarui, dan itu akan digambar ulang dengan benar. Ada banyak perpustakaan yang mengimplementasikan fungsi ini. Ini akan menjadi lebih nyaman bagi seseorang untuk menggunakan solusi siap pakai, misalnya, bereaksi-media , bereaksi-responsif , dll, dan untuk seseorang lebih mudah untuk menulis Anda sendiri .

Berbeda dengan ukuran layar, user-agentitu tidak dapat berubah secara dinamis saat aplikasi sedang berjalan (secara tegas, mungkin melalui alat pengembang, tetapi ini bukan skenario pengguna). Dalam hal ini, kami tidak perlu menggunakan logika kompleks dengan menyimpan nilai dan penghitungan ulang, cukup parsing string sekali window.navigator.userAgent,untuk menyimpan nilai, dan Anda selesai. Ada banyak perpustakaan untuk membantu Anda dengan hal ini, misalnya, deteksi seluler, reaksi-deteksi perangkat , dll.

Pendekatannya user-agentlebih sederhana, tetapi hanya menggunakannya saja tidak cukup. Siapa pun yang telah mengembangkan antarmuka adaptif secara serius tahu tentang "sentuhan ajaib" iPad dan perangkat serupa, yang pada posisi vertikal berada di bawah definisi seluler, dan horizontal - desktop, tetapi pada saat yang sama memiliki user-agentperangkat seluler. Perlu juga dicatat bahwa dalam aplikasi yang sepenuhnya adaptif / responsif, user-agent tidak mungkin untuk menentukan mobilitas berdasarkan informasi tentang hanya jika pengguna menggunakan, misalnya, browser desktop, tetapi telah meremas jendela ke ukuran "mobile".

Juga, jangan mengabaikan informasi tentang user-agent. Sangat sering dalam kode Anda dapat menemukan konstanta seperti isSafari,isIEdll. yang menangani "fitur" perangkat dan browser ini. Yang terbaik adalah menggabungkan kedua pendekatan.

Di basis kode kami, kami menggunakan konstanta isCheesySafariyang, seperti namanya, mendefinisikan keanggotaan user-agentdalam keluarga browser Safari. Namun selain itu, kami memiliki konstanta isSuperCheesySafari, yang menyiratkan Safari seluler yang sesuai dengan iOS versi 11, yang telah menjadi terkenal karena banyak bug seperti ini: https://hackernoon.com/how-to-fix-the-ios-11-input-element -in-fixed-modals-bug-aaf66c7ba3f8 .

export const isMobileUA = (() => magicParser(window.navigator.userAgent))()

import isMobileUA from './isMobileUA'

const MyComponent = (props) => {
 const { isMobile } = props

 return (isMobile || isMobileUA) ? <MobileComponent /> : <DesktopComponent />
}

Bagaimana dengan pertanyaan media? Ya, memang, CSS memiliki alat bawaan untuk bekerja dengan kemampuan beradaptasi: pertanyaan media dan metode analognya window.matchMedia. Mereka dapat digunakan, tetapi logika "memperbarui" komponen ketika mengubah ukuran masih harus diimplementasikan. Meskipun bagi saya pribadi, menggunakan sintaks kueri media daripada operasi perbandingan biasa di JS untuk logika aplikasi dan komponen adalah keuntungan yang meragukan.

Organisasi struktur komponen


Kami telah menemukan definisi mobilitas, sekarang mari kita renungkan penggunaan data yang telah kami peroleh dan pengaturan struktur kode komponen. Dalam kode kami, sebagai aturan, dua jenis komponen berlaku.

Jenis pertama adalah komponen, dipertajam baik di bawah ponsel, atau di bawah desktop. Dalam komponen seperti itu, nama-nama sering mengandung kata Mobile / Desktop, yang jelas menunjukkan bahwa komponen tersebut termasuk salah satu dari jenis. Sebagai contoh komponen semacam itu dapat dipertimbangkan <MobileList />dari @youla/ui.

import { Panel, Cell, Content, afterBorder } from './styled'
import Group from './Group'
import Button, { IMobileListButtonProps } from './Button'
import ContentOrButton, { IMobileListContentOrButton } from './ContentOrButton'
import Action, { IMobileListActionProps } from './Action'

export default { Panel, Group, Cell, Content, Button, ContentOrButton, Action }
export {
 afterBorder,
 IMobileListButtonProps,
 IMobileListContentOrButton,
 IMobileListActionProps
}

Komponen ini, selain sangat verbose ekspor, adalah daftar dengan data, pemisah, pengelompokan berdasarkan blok, dll. Desainer kami sangat menyukai komponen ini dan di mana-mana menggunakannya di antarmuka Ula. Misalnya, dalam deskripsi di halaman produk atau di fungsionalitas tarif baru kami:


Dan di N tempat di sekitar situs. Kami juga memiliki komponen serupa <DesktopList />yang mengimplementasikan fungsionalitas daftar ini untuk resolusi desktop.

Komponen tipe kedua berisi logika desktop dan seluler. Mari kita lihat versi sederhana dari rendering komponen kami <HeaderBoard />, yang hidup di @ youla / aplikasi-diklasifikasikan.

Kami telah menemukan untuk diriku sendiri sangat nyaman untuk membuat semua gaya-komponen untuk komponen dalam satu file dan impor di bawah ruang nama S, untuk memisahkan kode dari komponen lain: import * as S from ‘./styled’. Dengan demikian, "S" adalah objek yang kuncinya adalah nama-nama komponen yang ditata, dan nilainya adalah komponen itu sendiri.

 return (
   <HeaderWrapper>
     <Logo />
     {isMobile && <S.Arrow />}
     <S.Wraper isMobile={isMobile}>
       <Video src={bgVideo} />
       {!isMobile && <Header>{headerContent}</Header>}
       <S.WaveWrapper />
     </S.Wraper>
     {isMobile && <S.MobileHeader>{headerContent}</S.MobileHeader>}
     <Info link={link} />
     <PaintingInfo isMobile={isMobile} />
     {isMobile ? <CardsMobile /> : <CardsDesktop />}
     {isMobile ? <UserNavigation /> : <UserInfoModal />}
   </HeaderWrapper>
 )

Di sini isMobile, itu adalah ketergantungan komponen, atas dasar mana komponen itu sendiri akan memutuskan antarmuka mana yang akan di-render.

Untuk penskalaan yang lebih nyaman, kita sering menggunakan pola inversi kontrol di bagian yang digunakan kembali dari kode kita, tetapi hati-hati untuk tidak membebani abstraksi tingkat atas dengan logika yang tidak perlu.

Sekarang mari kita abstraksi sedikit dari komponen "Yulian" dan perhatikan lebih dekat kedua komponen ini:

  • <ComponentA />- Dengan pemisahan ketat antara desktop dan mobile logic.
  • <ComponentB />- digabungkan.

<ComponentA /> vs <ComponentB />


Struktur folder dan file root index.ts :

./ComponentA
- ComponentA.tsx
- ComponentADesktop.tsx
- ComponentAMobile.tsx
- index.ts
- styled.desktop.ts
- styled.mobile.ts


import ComponentA  from './ComponentA'
import ComponentAMobile  from './ComponentAMobile'
import ComponentADesktop  from './ComponentADesktop'

export default {
 ComponentACombined: ComponentA,
 ComponentAMobile,
 ComponentADesktop
}

Berkat webpack pengguncang pohon teknologi baru (atau menggunakan kolektor lain), Anda dapat membuang modul yang tidak digunakan ( ComponentADesktop, ComponentACombined), bahkan dengan ini ekspor kembali melalui file root:

import ComponentA from ‘@youla/ui’
<ComponentA.ComponentAMobile />

Hanya kode file ./ComponentAMobile yang masuk ke bundel terakhir.

Komponen <ComponentA />berisi impor asinkron menggunakan React.Lazyversi spesifik komponen <ComponentAMobile /> || <ComponentADesktop />untuk situasi tertentu.

Kami di Yule mencoba mematuhi pola titik masuk tunggal ke dalam komponen melalui file indeks. Ini membuat komponen menemukan dan refactoring lebih mudah. Jika konten komponen tidak diekspor kembali melalui file root, maka kita dapat mengeditnya dengan aman, karena kita tahu bahwa itu tidak digunakan di luar konteks komponen ini. Yah, naskah akan lindung nilai dalam keadaan darurat. Folder dengan komponen memiliki "antarmuka" sendiri: ekspor pada tingkat modul di file root, dan detail implementasinya tidak diungkapkan. Akibatnya, saat refactoring, Anda tidak perlu takut menyimpan antarmuka.

import React from 'react'

const ComponentADesktopLazy = React.lazy(() => import('./ComponentADesktop'))
const ComponentAMobileLazy = React.lazy(() => import('./ComponentAMobile'))

const ComponentA = (props) => {
 const { isMobile } = props

//    

 return (
   <React.Suspense fallback={props.fallback}>
     {isMobile ? (
       <ComponentAMobileLazy {...props} />
     ) : (
       <ComponentADesktopLazy {...props} />
     )}
   </React.Suspense>
 )
}

export default ComponentA

Selanjutnya, komponen tersebut <ComponentADesktop />berisi impor komponen desktop:

import React from 'react'

import { DesktopList, UserAuthDesktop, UserInfo } from '@youla/ui'

import Banner from '../Banner'

import * as S from './styled.desktop'

const ComponentADesktop = (props) => {
 const { user, items } = props

 return (
   <S.Wrapper>
     <S.Main>
       <Banner />
       <DesktopList items={items} />
     </S.Main>
     <S.SideBar>
       <UserAuthDesktop user={user} />
       <UserInfo user={user} />
     </S.SideBar>
   </S.Wrapper>
 )
}

export default ComponentADesktop

Komponen <ComponentAMobile />berisi impor komponen seluler:

import React from 'react'

import { MobileList, MobileTabs, UserAuthMobile } from '@youla/ui'

import * as S from './styled.mobile'

const ComponentAMobile = (props) => {
 const { user, items, tabs } = props

 return (
   <S.Wrapper>
     <S.Main>
       <UserAuthMobile user={user} />
       <MobileList items={items} />
       <MobileTabs tabs={tabs} />
     </S.Main>
   </S.Wrapper>
 )
}

export default ComponentAMobile

Komponennya <ComponentA />adaptif: dengan flag itu isMobiledapat memutuskan versi yang akan diundi, hanya dapat mengunduh file yang diperlukan secara serempak, yaitu, versi seluler dan desktop dapat digunakan secara terpisah.

Mari kita lihat komponennya sekarang <ComponentB />. Di dalamnya, kita tidak akan membusuk secara mendalam logika ponsel dan desktop, kita akan meninggalkan semua kondisi dalam kerangka satu fungsi. Demikian pula, kami tidak akan memisahkan komponen style.

Berikut adalah struktur foldernya. File root index.ts hanya mengekspor ulang ./ComponentB:

./ComponentB
- ComponentB.tsx
- index.ts
- styled.ts


export { default } from './ComponentB'

File ./ComponentB dengan komponen itu sendiri:


import React from 'react'

import {
 DesktopList,
 UserAuthDesktop,
 UserInfo,
 MobileList,
 MobileTabs,
 UserAuthMobile
} from '@youla/ui'

import * as S from './styled'

const ComponentB = (props) => {
 const { user, items, tabs, isMobile } = props

 if (isMobile) {
   return (
     <S.Wrapper isMobile={isMobile}>
       <S.Main isMobile={isMobile}>
         <UserAuthMobile user={user} />
         <MobileList items={items} />
         <MobileTabs tabs={tabs} />
       </S.Main>
     </S.Wrapper>
   )
 }

 return (
   <S.Wrapper>
     <S.Main>
       <Banner />
       <DesktopList items={items} />
     </S.Main>
     <S.SideBar>
       <UserAuthDesktop user={user} />
       <UserInfo user={user} />
     </S.SideBar>
   </S.Wrapper>
 )
}

export default ComponentB

Mari kita coba memperkirakan kelebihan dan kekurangan komponen ini.



Total tiga argumen pro dan kontra tersedot keluar dari jari mereka masing-masing. Ya, saya perhatikan bahwa beberapa kriteria disebutkan segera dalam keuntungan dan kerugian: ini dilakukan dengan sengaja, semua orang akan menghapusnya dari grup yang salah.

Pengalaman kami dengan @youla


Kami di perpustakaan komponen kami @ youla / ui berusaha untuk tidak menggabungkan komponen desktop dan seluler bersama-sama, karena ini adalah ketergantungan eksternal untuk banyak paket kami dan yang lainnya. Siklus hidup komponen-komponen ini selama mungkin, saya ingin menjaganya tetap ramping dan seringan mungkin.

Ada dua poin penting yang perlu diperhatikan .

Pertama, semakin kecil file JS rakitan, semakin cepat akan dikirim ke pengguna, ini jelas dan semua orang tahu. Tetapi karakteristik ini penting hanya untuk unduhan file pertama, selama kunjungan berulang file akan dikirim dari cache, dan tidak akan ada masalah pengiriman kode.

Di sini kita beralih ke alasan nomor dua, yang mungkin segera menjadi, atau sudah menjadi, masalah utama aplikasi web besar. Banyak yang sudah menebak: ya, kita berbicara tentang durasi penguraian.

Mesin modern seperti V8 dapat melakukan cache dan hasil parsing, tetapi sejauh ini tidak berfungsi dengan sangat efisien. Eddie Osmani memiliki artikel bagus tentang topik ini: https://v8.dev/blog/cost-of-javascript-2019 . Anda juga dapat berlangganan blog V8: https://twitter.com/v8js .

Ini adalah durasi penguraian yang akan kami kurangi secara signifikan, ini sangat penting untuk perangkat seluler dengan prosesor yang lemah .

Dalam paket aplikasi @ youla-web / app- * pengembangannya lebih "berorientasi bisnis". Dan demi kecepatan / kesederhanaan / preferensi pribadi, keputusan dipilih bahwa pengembang sendiri menganggap yang paling benar dalam situasi ini. Sering terjadi bahwa ketika mengembangkan fitur MVP kecil, lebih baik menulis versi yang lebih sederhana dan lebih cepat (<ComponentB />), dalam komponen seperti itu ada beberapa baris. Dan, seperti yang kita tahu, semakin banyak kode - semakin banyak kesalahan.

Setelah memeriksa relevansi fitur, dimungkinkan untuk mengganti komponen dengan versi yang lebih optimal dan produktif <ComponentA />, jika perlu.

Saya juga menyarankan Anda untuk mengintip komponen. Jika UI dari versi seluler dan desktop sangat berbeda, maka mungkin mereka harus dipisahkan, menjaga beberapa logika umum di satu tempat. Ini akan memungkinkan Anda untuk menghilangkan rasa sakit saat menulis CSS yang kompleks, masalah dengan kesalahan di salah satu tampilan ketika refactoring atau mengubah yang lain. Dan sebaliknya, jika UI sedekat mungkin, lalu mengapa bekerja ekstra?

Kesimpulan


Untuk meringkas. Kami memahami terminologi antarmuka adaptif / responsif, memeriksa beberapa cara untuk menentukan mobilitas dan beberapa opsi untuk mengatur struktur kode komponen adaptif, dan mengidentifikasi kelebihan dan kekurangan masing-masing. Tentunya banyak hal di atas sudah diketahui oleh Anda, tetapi pengulangan adalah cara terbaik untuk melakukan konsolidasi. Saya harap Anda belajar sesuatu yang baru untuk diri Anda sendiri. Lain kali kami ingin menerbitkan sekumpulan rekomendasi untuk menulis aplikasi web progresif, dengan tips mengatur, menggunakan kembali, dan memelihara kode.

All Articles