متجاوب أم متجاوب؟ تحليل مكونات رد الفعل تحليل



في هذه المقالة ، سوف نفهم مدى تعقيد كتابة المكونات التكيفية ، والحديث عن تقسيم الكود ، والنظر في عدة طرق لتنظيم بنية الكود ، وتقييم مزاياها وعيوبها ، ومحاولة اختيار الأفضل (ولكن هذا ليس دقيقًا).

أولاً ، دعنا نتعامل مع المصطلحات. كثيرا ما نسمع شروط التكيف و الاستجابة . ماذا يقصدون؟ ماهو الفرق؟ كيف يرتبط هذا بمكوناتنا؟

تعد Adaptive (التكيفية) مجموعة معقدة من الواجهات المرئية التي تم إنشاؤها لأحجام شاشات معينة. استجابة (استجابة) هي واجهة واحدة تتكيف مع أي حجم الشاشة.

علاوة على ذلك ، عندما تتحلل الواجهة إلى أجزاء صغيرة ، يصبح الفرق بين التكيف والاستجابة أكثر وضوحًا ، حتى يختفي تمامًا.

عند تطوير التخطيطات ، غالبًا ما لا يشارك المصممون ، وكذلك المطورون ، هذه المفاهيم ويجمعون بين المنطق التكيفي والاستجابة.

علاوة على ذلك ، سأسمي المكونات التي تحتوي على منطق تكيفي ومتجاوب على أنها ببساطة قابلة للتكيف . أولاً ، لأنني أحب هذه الكلمة أكثر من "استجابة" ، أو سامحني ، "استجابة". وثانيًا ، أجده أكثر شيوعًا.

سأركز على مجالين لواجهات العرض - الهاتف المحمول وسطح المكتب. نقصد بعرض الجوال أن العرض ، على سبيل المثال ، 1 991 بكسل(الرقم نفسه ليس مهمًا ، إنه مجرد ثابت ، والذي يعتمد على نظام التصميم والتطبيق الخاص بك) ، وتحت عرض سطح المكتب - يكون العرض أكبر من العتبة المحددة. سأفتقد عن قصد الشاشات اللوحية وشاشات العرض العريضة ، لأنه ، أولاً ، لا يحتاجها الجميع ، وثانيًا ، سيكون من الأسهل وضعها بهذه الطريقة. لكن الأنماط التي سنتحدث عنها تتوسع بالتساوي لأي عدد من "التعيينات".

أيضًا ، لن أتحدث تقريبًا عن CSS ، بشكل رئيسي سنتحدث عن منطق المكون.

الواجهة الأمامية @ youla


سأتحدث باختصار عن مجموعتنا في Yulia حتى يتضح في أي ظروف نقوم بإنشاء مكوناتنا. نحن نستخدم تتفاعل / مسترجع ، ونحن نعمل في monorep، ونحن نستخدم نسخة مطبوعة على الآلة الكاتبة ونكتب CSS على -مكونات نصب . كمثال ، دعنا نلقي نظرة على حزمنا الثلاث (الحزم في مفهوم monoreps هي حزم NPM المترابطة ، والتي يمكن أن تكون تطبيقات منفصلة أو مكتبات أو مرافق أو مكونات - تختار درجة التحلل بنفسك). سنلقي نظرة على تطبيقين ومكتبة واجهة مستخدم واحدة.

@ youla / ui- مكتبة المكونات. يتم استخدامها ليس فقط من قبلنا ، ولكن أيضًا من قبل فرق أخرى تحتاج إلى واجهات "Yulian". تحتوي المكتبة على الكثير من الأشياء ، بدءًا من الأزرار وحقول الإدخال ، وتنتهي ، على سبيل المثال ، برأس أو نموذج تفويض (بتعبير أدق ، جزء واجهة المستخدم الخاص بها). نعتبر هذه المكتبة تبعية خارجية لتطبيقنا.

@ youla-web / app-تصنيف - التطبيق المسؤول عن أقسام الكتالوج / المنتج / التفويض. وفقًا لمتطلبات العمل ، يجب أن تكون جميع الواجهات هنا قابلة للتكيف .

@ youla-web / app-b2b هو التطبيق المسؤول عن أقسام حسابك الشخصي للمستخدمين المحترفين. واجهات هذا التطبيق هي حصرية سطح المكتب .

علاوة على ذلك ، سننظر في كتابة المكونات التكيفية باستخدام مثال هذه الحزم. ولكن عليك أولاً التعامل معها isMobile.

تعريف التنقل هو Mobile && <Component />


import React from 'react'

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

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

قبل البدء في كتابة المكونات التكيفية ، تحتاج إلى معرفة كيفية تحديد "التنقل". هناك العديد من الطرق لتنفيذ تعريف التنقل. أريد أن أتطرق إلى بعض النقاط الرئيسية.

تحديد التنقل حسب عرض الشاشة ووكيل المستخدم


يعرف معظمكم جيدًا كيفية تنفيذ كلا الخيارين ، ولكن دعنا نتناول النقاط الرئيسية لفترة وجيزة مرة أخرى.

عند العمل مع عرض الشاشة ، من المعتاد تعيين نقاط الحدود ، وبعد ذلك يجب أن يتصرف التطبيق كنقطة متحركة أو سطح مكتب. الإجراء كما يلي:

  1. أنشئ ثوابت بنقاط حدود واحفظها في الموضوع (إذا كان حل CSS يسمح بذلك). قد تكون القيم نفسها هي ما يجده المصممون الأكثر ملاءمة لنظام واجهة المستخدم الخاص بك .
  2. نحفظ حجم الشاشة الحالي في redux / mobx / السياق / أي مصدر بيانات. في أي مكان ، إذا كانت المكونات فقط ، ويفضل أن يكون منطق التطبيق لديه حق الوصول إلى هذه البيانات.
  3. نحن نشترك في حدث تغيير الحجم ونحدّث قيمة عرض الشاشة إلى القيمة التي ستؤدي إلى تشغيل سلسلة تحديثات شجرة المكونات.
  4. نقوم بإنشاء وظائف مساعد بسيطة تقوم باستخدام عرض الشاشة والثوابت بحساب الحالة الحالية ( isMobile،isDesktop ).

إليك الشفرة الزائفة التي تنفذ نموذج العمل هذا:

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)

عندما تتغير الشاشة ، propsسيتم تحديث قيم المكون ، وسيتم إعادة رسمها بشكل صحيح. هناك العديد من المكتبات التي تنفذ هذه الوظيفة. سيكون من الملائم أكثر أن يستخدم شخص ما حلًا جاهزًا ، على سبيل المثال ، وسائط تفاعلية ، تفاعلية الاستجابة ، وما إلى ذلك ، وبالنسبة لشخص ما ، من الأسهل كتابة حل خاص بك.

على عكس حجم الشاشة ، user-agentلا يمكن تغييره ديناميكيًا أثناء تشغيل التطبيق (بشكل دقيق ، ربما من خلال أدوات المطور ، ولكن هذا ليس سيناريو للمستخدم). في هذه الحالة ، لا نحتاج إلى استخدام منطق معقد لتخزين القيمة وإعادة الحساب ، فقط قم بتحليل السلسلة مرة واحدة window.navigator.userAgent,لحفظ القيمة ، وبذلك تكون قد انتهيت. هناك مجموعة من المكتبات لمساعدتك في ذلك ، على سبيل المثال ، اكتشاف الجوّال، والكشف عن جهاز التفاعل ، وما إلى ذلك.

النهج user-agentأبسط ، ولكن مجرد استخدامه لا يكفي. أي شخص قام بتطوير واجهات تكيفية على محمل الجد يعرف عن "التطور السحري" لأجهزة iPad والأجهزة المماثلة ، والتي تقع في الوضع الرأسي تحت تعريف الهاتف المحمول ، وفي الوضع الأفقي - سطح المكتب ، ولكن في نفس الوقت لديه user-agentجهاز محمول. من الجدير بالذكر أيضًا أنه في التطبيق التكيفي / المتجاوب تمامًا ، user-agent من المستحيل تحديد التنقل استنادًا إلى معلومات حول ما إذا كان المستخدم يستخدم ، على سبيل المثال ، متصفح سطح مكتب ، ولكنه ضغط النافذة إلى حجم "الجوال".

أيضا ، لا تهمل معلومات حول user-agent. في كثير من الأحيان تجد في الشفرة ثوابت مثل isSafari،isIEإلخ التي تتعامل مع "ميزات" هذه الأجهزة والمتصفحات. من الأفضل الجمع بين كلا النهجين.

في قاعدة التعليمات البرمجية الخاصة بنا ، نستخدم ثابتًا isCheesySafari، كما يوحي الاسم ، يحدد العضوية user-agentفي عائلة متصفح Safari. ولكن إلى جانب ذلك ، لدينا ثابت isSuperCheesySafari، مما يعني أن Safari المحمول يتوافق مع الإصدار 11 من iOS ، والذي أصبح مشهورًا للعديد من الأخطاء مثل هذا: https://hackernoon.com/how-to-fix-the-ios-11-input-element - في - إصلاحات مشروطة - خطأ - aaf66c7ba3f8 .

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

import isMobileUA from './isMobileUA'

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

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

ماذا عن الاستفسارات الإعلامية؟ نعم ، بالفعل ، يحتوي CSS على أدوات مدمجة للعمل مع القدرة على التكيف: استعلامات الوسائط وطريقتها التناظرية window.matchMedia. يمكن استخدامها ، ولكن لا يزال يتعين تنفيذ منطق "تحديث" المكونات عند تغيير الحجم. على الرغم من أنني شخصيًا ، فإن استخدام بناء جملة استعلامات الوسائط بدلاً من عمليات المقارنة المعتادة في JS لمنطق التطبيق ومكوناته هو ميزة مشكوك فيها.

تنظيم هيكل المكونات


لقد توصلنا إلى تعريف التنقل ، والآن دعونا نفكر في استخدام البيانات التي حصلنا عليها وتنظيم هيكل رمز المكون. يسود نوعان من المكونات في التعليمات البرمجية الخاصة بنا.

النوع الأول هو المكونات ، التي يتم شحذها إما تحت الهاتف الخلوي أو تحت سطح المكتب. في مثل هذه المكونات ، غالبًا ما تحتوي الأسماء على الكلمات Mobile / Desktop ، والتي تشير بوضوح إلى أن المكون ينتمي إلى أحد الأنواع. كمثال لمثل هذا المكون يمكن اعتباره <MobileList />من @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
}

هذا المكون ، بالإضافة إلى التصدير المطول للغاية ، عبارة عن قائمة بالبيانات والفواصل والتجمعات حسب الكتل وما إلى ذلك. إن مصممينا مغرمون جدًا بهذا المكون ويستخدمونه في كل مكان في واجهات Ula. على سبيل المثال ، في الوصف الموجود على صفحة المنتج أو في وظيفة التعرفة الجديدة الخاصة بنا:


وفي N من الأماكن حول الموقع. لدينا أيضًا مكون مماثل <DesktopList />يطبق وظيفة القائمة هذه لحل سطح المكتب.

تحتوي مكونات النوع الثاني على منطق كل من سطح المكتب والجوّال. دعونا نلقي نظرة على نسخة مبسطة من عرض <HeaderBoard />مكوننا ، الذي يعيش في @ youla / تصنيف التطبيق.

لقد وجدت لنفسي أمر مريح جدا لجعل كل المكونات على غرار لمكون في ملف واحد واستيراده تحت مساحات S، لفصل رمز من المكونات الأخرى: import * as S from ‘./styled’. وفقًا لذلك ، "S" هو كائن مفاتيحه هي أسماء المكونات ذات النمط ، والقيم هي المكونات نفسها.

 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>
 )

هنا isMobile، تعتمد على المكون ، على أساسه سيقرر المكون نفسه الواجهة التي سيتم عرضها.

لمزيد من القياس المريح ، غالبًا ما نستخدم نمط عكس التحكم في الأجزاء المعاد استخدامها من التعليمات البرمجية الخاصة بنا ، ولكن كن حذرًا من الإفراط في تجريد المستوى الأعلى بمنطق غير ضروري.

دعونا الآن نلخص أنفسنا قليلاً من مكونات "Yulian" ونلقي نظرة فاحصة على هذين المكونين:

  • <ComponentA />- مع الفصل الدقيق بين منطق سطح المكتب والجوال.
  • <ComponentB />- مجتمعة.

<ComponentA /> مقابل <ComponentB />


هيكل المجلد وملف 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
}

بفضل حزمة الويب الجديدة التي تهز الأشجار (أو باستخدام أي جامع آخر) ، يمكنك تجاهل الوحدات غير المستخدمة ( ComponentADesktop، ComponentACombined) ، حتى مع إعادة التصدير هذه عبر الملف الجذر:

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

يدخل رمز ملف ./ComponentAMobile فقط في الحزمة النهائية. يحتوي

المكون <ComponentA />على عمليات استيراد غير متزامنة باستخدام React.Lazyإصدار محدد من المكون <ComponentAMobile /> || <ComponentADesktop />لحالة معينة.

نحن في Yule نحاول الالتزام بنمط نقطة إدخال واحدة في المكون من خلال ملف الفهرس. وهذا يجعل العثور على المكونات وإعادة هيكلتها أسهل. إذا لم يتم إعادة تصدير محتويات المكون من خلال الملف الجذر ، فيمكننا تحريره بأمان ، لأننا نعلم أنه لا يستخدم خارج سياق هذا المكون. حسنًا ، سيُحَوَّل الطباع النصفي قليلًا. يحتوي المجلد الذي يحتوي على المكون على "واجهة" خاصة به: لا يتم الإفصاح عن عمليات التصدير على مستوى الوحدة النمطية في الملف الجذر ، وتفاصيل التنفيذ الخاصة به. ونتيجة لذلك ، عند إعادة البيع ، لا يمكنك أن تخاف من حفظ الواجهة.

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

علاوة على ذلك ، <ComponentADesktop />يحتوي المكون على استيراد مكونات سطح المكتب:

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

<ComponentAMobile />يحتوي المكون على استيراد مكونات متحركة:

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

المكون قابل <ComponentA />للتكيف: من خلال العلم ، isMobileيمكنه تحديد الإصدار الذي يتم رسمه ، ويمكنه فقط تنزيل الملفات المطلوبة بشكل غير متزامن ، أي أنه يمكن استخدام إصدارات الجوال وسطح المكتب بشكل منفصل.

دعونا نلقي نظرة على المكون الآن <ComponentB />. في ذلك ، لن نتحلل بعمق المنطق المحمول وسطح المكتب ، سنترك جميع الشروط في إطار وظيفة واحدة. وبالمثل ، لن نفصل مكونات الأنماط.

هنا هيكل المجلد. يقوم ملف index.ts root بإعادة التصدير ./ComponentB:

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


export { default } from './ComponentB'

ملف ./ComponentB مع المكون نفسه:


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

دعونا نحاول تقدير مزايا وعيوب هذه المكونات.



مجموع ثلاثة إيجابيات وحجج تمتص من كل إصبع. نعم ، لقد لاحظت أن بعض المعايير مذكورة على الفور في كل من المزايا والعيوب: تم ​​ذلك عن قصد ، وسيحذفها الجميع من المجموعة الخطأ.

تجربتنا معYoula


نحن في مكتبة المكونات الخاصة بنا @ youla / ui نحاول ألا نمزج مكونات سطح المكتب والمحمول معًا ، لأن هذا هو تبعية خارجية للعديد من حزمنا وغيرها. دورة حياة هذه المكونات أطول فترة ممكنة ، أريد أن أحافظ عليها ضئيلة وخفيفة قدر الإمكان.

هناك نقطتان مهمتان يجب ملاحظتهما .

أولاً ، كلما كان ملف JS المجمع أصغر ، كلما تم تسليمه بشكل أسرع للمستخدم ، وهذا واضح والجميع يعرف. لكن هذه الخاصية مهمة فقط للتنزيل الأول للملف ، خلال الزيارات المتكررة سيتم تسليم الملف من ذاكرة التخزين المؤقت ، ولن تكون هناك مشكلة في تسليم الرمز.

هنا ننتقل إلى السبب الثاني ، الذي قد يصبح قريبًا أو أصبح بالفعل المشكلة الرئيسية لتطبيقات الويب الكبيرة. لقد خمّن الكثير بالفعل: نعم ، نحن نتحدث عن مدة التحليل.

يمكن للمحركات الحديثة مثل V8 التخزين المؤقت ونتيجة التحليل ، لكنها حتى الآن لا تعمل بكفاءة عالية. إيدي عثماني لديه مقال رائع حول هذا الموضوع: https://v8.dev/blog/cost-of-javascript-2019 . يمكنك أيضًا الاشتراك في مدونة V8: https://twitter.com/v8js .

إنها مدة التحليل التي سنقللها بشكل كبير ، وهذا مهم بشكل خاص للأجهزة المحمولة ذات المعالجات الضعيفة .

في حزم التطبيقات @ youla-web / app- * يكون التطوير "أكثر توجهاً نحو الأعمال". ومن أجل السرعة / البساطة / التفضيلات الشخصية ، يتم اختيار القرار الذي يعتبره المطور نفسه الأكثر صحة في هذا الموقف. غالبًا ما يحدث أنه عند تطوير ميزات MVP الصغيرة ، من الأفضل أولاً كتابة نسخة أبسط وأسرع (<ComponentB />) ، في مثل هذا المكون يوجد نصف عدد الأسطر. وكما نعلم ، كلما زادت الشفرة - زادت الأخطاء.

بعد التحقق من ملاءمة الميزة ، سيكون من الممكن استبدال المكون بإصدار أكثر إنتاجية وتحسينًا <ComponentA /> ، إذا لزم الأمر.

أنصحك أيضًا بإلقاء نظرة خاطفة على المكون. إذا كانت واجهات المستخدم الخاصة بإصدارات الجوال وسطح المكتب مختلفة تمامًا ، فربما يجب فصلها ، مع الاحتفاظ ببعض المنطق المشترك في مكان واحد. سيسمح لك ذلك بالتخلص من الألم عند كتابة CSS المعقدة ، ومشاكل الأخطاء في أحد الشاشات عند إعادة هيكلة أو تغيير آخر. والعكس صحيح ، إذا كانت واجهة المستخدم قريبة قدر الإمكان ، فلماذا تقوم بالعمل الإضافي؟

استنتاج


كي تختصر. لقد فهمنا مصطلحات الواجهة التكيفية / المتجاوبة ، وفحصنا عدة طرق لتحديد التنقل والعديد من الخيارات لتنظيم بنية الكود للمكون التكيفي ، وحددنا مزايا وعيوب كل منها. من المؤكد أن الكثير مما سبق سبق معرفته لك ، لكن التكرار هو أفضل طريقة للدمج. آمل أن تكون قد تعلمت شيئًا جديدًا لنفسك. في المرة القادمة نريد نشر مجموعة من التوصيات لكتابة تطبيقات الويب التقدمية ، مع نصائح حول تنظيم التعليمات البرمجية وإعادة استخدامها وصيانتها.

All Articles