Reaktionsschnell oder reaktionsschnell? Struktur der Analyse-Reaktionskomponente



In diesem Artikel werden wir die Komplexität des Schreibens adaptiver Komponenten verstehen, über das Aufteilen von Code sprechen, verschiedene Möglichkeiten zum Organisieren der Codestruktur in Betracht ziehen, ihre Vor- und Nachteile bewerten und versuchen, die beste auszuwählen (dies ist jedoch nicht korrekt).

Lassen Sie uns zunächst die Terminologie behandeln. Wir hören oft die Begriffe adaptiv und reaktionsschnell . Was meinen sie? Was ist der Unterschied? Wie hängt das mit unseren Komponenten zusammen?

Adaptiv (adaptiv) ist ein Komplex visueller Schnittstellen, die für bestimmte Bildschirmgrößen erstellt wurden. Responsive (Responsive) ist eine einzelne Oberfläche, die sich an jede Bildschirmgröße anpasst.

Wenn die Schnittstelle in kleine Fragmente zerlegt wird, wird der Unterschied zwischen adaptiv und reaktionsfreudig immer unschärfer, bis er vollständig verschwindet.

Bei der Entwicklung von Layouts teilen unsere Designer und Entwickler diese Konzepte meistens nicht und kombinieren adaptive und reaktionsschnelle Logik.

Außerdem werde ich Komponenten, die adaptive und reaktionsfähige Logik enthalten, als einfach adaptiv bezeichnen . Erstens, weil ich dieses Wort mehr mag als "reaktionsschnell" oder, verzeih mir, "reaktionsschnell". Und zweitens finde ich es häufiger.

Ich werde mich auf zwei Bereiche der Display-Schnittstellen konzentrieren - Mobile und Desktop. Mit mobiler Anzeige meinen wir beispielsweise die Breite ≤ 991 Pixel(Die Zahl selbst ist nicht wichtig, sondern nur eine Konstante, die von Ihrem Designsystem und Ihrer Anwendung abhängt.) Unter der Desktop-Anzeige ist die Breite größer als der ausgewählte Schwellenwert. Ich werde absichtlich Displays für Tablets und Breitbildmonitore vermissen, weil erstens nicht jeder sie braucht, und zweitens wird es einfacher sein, es so auszudrücken. Aber die Muster, über die wir sprechen werden, erweitern sich gleichermaßen für eine beliebige Anzahl von "Mappings".

Außerdem werde ich fast nicht über CSS sprechen, sondern hauptsächlich über Komponentenlogik.

Frontend @youla


Ich werde kurz auf unseren Stack in Yulia eingehen, damit klar wird, unter welchen Bedingungen wir unsere Komponenten erstellen. Wir verwenden React / Redux , wir arbeiten in Monorep, wir verwenden Typescript und wir schreiben CSS für gestylte Komponenten . Schauen wir uns als Beispiel unsere drei Pakete an (Pakete im Konzept von Monoreps sind miteinander verbundene NPM-Pakete, bei denen es sich um separate Anwendungen, Bibliotheken, Dienstprogramme oder Komponenten handeln kann - Sie bestimmen den Zerlegungsgrad selbst). Wir werden uns zwei Anwendungen und eine UI-Bibliothek ansehen.

@ youla / ui- Bibliothek von Komponenten. Sie werden nicht nur von uns verwendet, sondern auch von anderen Teams, die "Yulian" -Schnittstellen benötigen. Die Bibliothek hat viele Dinge, angefangen mit Schaltflächen und Eingabefeldern bis hin zu einem Header oder einem Autorisierungsformular (genauer gesagt dem UI-Teil). Wir betrachten diese Bibliothek als externe Abhängigkeit unserer Anwendung.

@ youla-web / app-klassifiziert - die Anwendung, die für die Abschnitte des Katalogs / Produkts / der Autorisierung verantwortlich ist. Entsprechend den Geschäftsanforderungen sollten alle Schnittstellen hier anpassungsfähig sein .

@ youla-web / app-b2b ist die Anwendung, die für die Abschnitte Ihres persönlichen Kontos für professionelle Benutzer verantwortlich ist. Die Schnittstellen dieser Anwendung sind ausschließlich Desktop- Schnittstellen .

Weiterhin werden wir erwägen, adaptive Komponenten am Beispiel dieser Pakete zu schreiben. Aber zuerst müssen Sie sich darum kümmern isMobile.

Mobilitätsdefinition istMobile && <Komponente />


import React from 'react'

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

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

Bevor Sie mit dem Schreiben adaptiver Komponenten beginnen, müssen Sie lernen, wie Sie „Mobilität“ definieren. Es gibt viele Möglichkeiten, die Definition von Mobilität umzusetzen. Ich möchte auf einige wichtige Punkte eingehen.

Ermittlung der Mobilität nach Bildschirmbreite und User-Agent


Die meisten von Ihnen wissen gut, wie man beide Optionen implementiert, aber lassen Sie uns noch einmal kurz auf die wichtigsten Punkte eingehen.

Wenn Sie mit der Breite des Bildschirms arbeiten, ist es üblich, Grenzpunkte festzulegen. Danach sollte sich die Anwendung wie eine mobile oder eine Desktop-Anwendung verhalten. Das Verfahren ist wie folgt:

  1. Erstellen Sie Konstanten mit Grenzpunkten und speichern Sie sie im Betreff (sofern Ihre CSS-Lösung dies zulässt). Die Werte selbst sind möglicherweise das, was Ihre Designer für Ihr UI-System am besten geeignet finden .
  2. Wir speichern die aktuelle Bildschirmgröße in einem Redux / Mobx / Kontext / einer beliebigen Datenquelle. Überall, wenn nur die Komponenten und vorzugsweise die Anwendungslogik Zugriff auf diese Daten hatten.
  3. Wir abonnieren das Größenänderungsereignis und aktualisieren den Wert der Bildschirmbreite auf den Wert, der die Aktualisierungskette des Komponentenbaums auslöst.
  4. Wir erstellen einfache Hilfsfunktionen, die anhand von Bildschirmbreiten und Konstanten den aktuellen Status ( isMobile,isDesktop ) berechnen .

Hier ist der Pseudocode, der dieses Arbeitsmodell implementiert:

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)

Wenn sich der Bildschirm ändert, werden die Werte propsfür die Komponente aktualisiert und korrekt neu gezeichnet. Es gibt viele Bibliotheken, die diese Funktionalität implementieren. Es ist für jemanden bequemer, eine vorgefertigte Lösung zu verwenden, z. B. React-Media , React-Responsive usw., und für jemanden ist es einfacher, eine eigene zu schreiben.

Im Gegensatz zur Bildschirmgröße kann user-agentsie sich während der Ausführung der Anwendung nicht dynamisch ändern (genau genommen möglicherweise über die Tools des Entwicklers, dies ist jedoch kein Benutzerszenario). In diesem Fall müssen wir keine komplexe Logik zum Speichern des Werts und zum Nachzählen verwenden. Analysieren Sie die Zeichenfolge nur einmal window.navigator.userAgent,, um den Wert zu speichern, und fertig. Es gibt eine Reihe von Bibliotheken, die Ihnen dabei helfen, beispielsweise die mobile Erkennung, React-Device-Detection usw.

Der Ansatz ist user-agenteinfacher, aber es reicht nicht aus, ihn nur zu verwenden. Jeder, der ernsthaft adaptive Schnittstellen entwickelt hat, kennt die „magische Wendung“ von iPads und ähnlichen Geräten, die in vertikaler Position unter die Definition von mobil und horizontal fallen - Desktop, aber gleichzeitig ein user-agentmobiles Gerät haben. Es ist auch erwähnenswert, dass es in einer vollständig adaptiven / reaktionsschnellen Anwendung user-agent unmöglich ist, die Mobilität anhand von Informationen zu bestimmen, die nur dann vorliegen, wenn der Benutzer beispielsweise einen Desktop-Browser verwendet, das Fenster jedoch auf die Größe „mobil“ gedrückt hat.

Vernachlässigen Sie auch nicht die Informationen über user-agent. Sehr oft finden Sie im Code Konstanten wie isSafari:isIEusw., die die "Funktionen" dieser Geräte und Browser handhaben. Es ist am besten, beide Ansätze zu kombinieren.

In unserer Codebasis verwenden wir eine Konstante isCheesySafari, die, wie der Name schon sagt, die Mitgliedschaft user-agentin der Safari-Browserfamilie definiert . Abgesehen davon haben wir eine Konstante isSuperCheesySafari, die eine mobile Safari impliziert, die iOS Version 11 entspricht und für viele Fehler wie diesen berühmt geworden ist: 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 />
}

Was ist mit Medienabfragen? Ja, in der Tat verfügt CSS über integrierte Tools für die Arbeit mit Anpassungsfähigkeit: Medienabfragen und ihre analoge Methode window.matchMedia. Sie können verwendet werden, aber die Logik des „Aktualisierens“ von Komponenten beim Ändern der Größe muss noch implementiert werden. Obwohl für mich persönlich die Verwendung der Syntax von Medienabfragen anstelle der in JS üblichen Vergleichsoperationen für Anwendungslogik und Komponenten ein zweifelhafter Vorteil ist.

Organisation der Komponentenstruktur


Wir haben die Definition von Mobilität herausgefunden. Lassen Sie uns nun über die Verwendung der erhaltenen Daten und die Organisation der Komponentencodestruktur nachdenken. In unserem Code herrschen in der Regel zwei Arten von Komponenten vor.

Der erste Typ sind die Komponenten, die entweder unter dem Mobiltelefon oder unter dem Desktop geschärft werden. In solchen Komponenten enthalten die Namen häufig die Wörter Mobile / Desktop, was eindeutig darauf hinweist, dass die Komponente zu einem der Typen gehört. Als Beispiel kann eine solche Komponente <MobileList />aus betrachtet werden @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
}

Diese Komponente ist neben dem sehr ausführlichen Export eine Liste mit Daten, Trennzeichen, Gruppierungen nach Blöcken usw. Unsere Designer lieben diese Komponente sehr und verwenden sie überall in den Ula-Schnittstellen. Zum Beispiel in der Beschreibung auf der Produktseite oder in unserer neuen Tariffunktion:


Und an N Stellen auf der Website. Wir haben auch eine ähnliche Komponente <DesktopList />, die diese Listenfunktion für die Desktopauflösung implementiert.

Die Komponenten des zweiten Typs enthalten die Logik von Desktop und Mobile. Schauen wir uns eine vereinfachte Version des Renderings unserer Komponente an <HeaderBoard />, die sich in @ youla / app-klassifiziert befindet.

Wir haben festgestellt, dass es für mich sehr praktisch ist, alle gestalteten Komponenten für eine Komponente in einer einzigen Datei zu erstellen und unter den Namespaces S zu importieren, um den Code von den anderen Komponenten zu trennen : import * as S from ‘./styled’. Dementsprechend ist "S" ein Objekt, dessen Schlüssel die Namen der gestalteten Komponenten sind, und die Werte sind die Komponenten selbst.

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

Hier isMobileist es die Abhängigkeit der Komponente, auf deren Grundlage die Komponente selbst entscheidet, welche Schnittstelle gerendert werden soll.

Für eine bequemere Skalierung verwenden wir häufig das Steuerungsinversionsmuster in den wiederverwendeten Teilen unseres Codes. Achten Sie jedoch darauf, die Abstraktionen der obersten Ebene nicht mit unnötiger Logik zu überladen.

Lassen Sie uns nun ein wenig von den "Yulian" -Komponenten abstrahieren und diese beiden Komponenten genauer betrachten:

  • <ComponentA />- mit einer strikten Trennung von Desktop- und mobiler Logik.
  • <ComponentB />- kombiniert.

<ComponentA /> vs <ComponentB />


Ordnerstruktur und Stammdatei 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
}

Dank der neuen Technologie, die das Webpack erschüttert (oder einen anderen Kollektor verwendet), können Sie nicht verwendete Module ( ComponentADesktop, ComponentACombined) auch bei diesem Reexport über die Stammdatei verwerfen :

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

Nur der Dateicode ./ComponentAMobile wird in das endgültige Bundle aufgenommen.

Die Komponente <ComponentA />enthält asynchrone Importe mit einer React.Lazybestimmten Version der Komponente <ComponentAMobile /> || <ComponentADesktop />für eine bestimmte Situation.

Wir bei Yule versuchen, das Muster eines einzelnen Einstiegspunkts in die Komponente über die Indexdatei einzuhalten. Dies erleichtert das Auffinden und Umgestalten von Komponenten. Wenn der Inhalt der Komponente nicht über die Stammdatei erneut exportiert wird, können wir ihn sicher bearbeiten, da wir wissen, dass er nicht außerhalb des Kontexts dieser Komponente verwendet wird. Nun, Typescript wird sich zur Not absichern. Der Ordner mit der Komponente verfügt über eine eigene „Schnittstelle“: Exportiert auf Modulebene in die Stammdatei, und die Implementierungsdetails werden nicht bekannt gegeben. Daher können Sie beim Refactoring keine Angst haben, die Schnittstelle zu speichern.

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

Darüber hinaus <ComponentADesktop />enthält die Komponente den Import von Desktop-Komponenten:

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

Eine Komponente <ComponentAMobile />enthält den Import mobiler Komponenten:

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

Die Komponente ist <ComponentA />anpassungsfähig: Durch das Flag isMobilekann sie entscheiden, welche Version gezeichnet werden soll, die erforderlichen Dateien können nur asynchron heruntergeladen werden, dh die mobile und die Desktop-Version können getrennt verwendet werden.

Schauen wir uns jetzt die Komponente an <ComponentB />. Darin werden wir die mobile und Desktop-Logik nicht tief zerlegen, sondern alle Bedingungen im Rahmen einer Funktion belassen. Ebenso werden wir die Komponenten von Stilen nicht trennen.

Hier ist die Ordnerstruktur. Die Datei root index.ts wird einfach erneut exportiert ./ComponentB:

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


export { default } from './ComponentB'

Die ./ComponentB-Datei mit der Komponente selbst:


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

Versuchen wir, die Vor- und Nachteile dieser Komponenten abzuschätzen.



Insgesamt drei Vor- und Nachteile, die jedem von ihnen aus dem Finger gerissen wurden. Ja, ich habe festgestellt, dass einige Kriterien sowohl in ihren Vor- als auch in ihren Nachteilen sofort erwähnt werden: Dies wurde absichtlich gemacht, jeder wird sie aus der falschen Gruppe löschen.

Unsere Erfahrung mit @youla


Wir in unserer Komponentenbibliothek @ youla / ui versuchen, Desktop- und mobile Komponenten nicht miteinander zu mischen, da dies für viele unserer Pakete und andere eine externe Abhängigkeit darstellt. Der Lebenszyklus dieser Komponenten ist so lang wie möglich, ich möchte sie so schlank und leicht wie möglich halten.

Es gibt zwei wichtige Punkte zu beachten .

Erstens, je kleiner die zusammengestellte JS-Datei ist, desto schneller wird sie an den Benutzer geliefert. Dies ist offensichtlich und jeder weiß es. Diese Eigenschaft ist jedoch nur für den ersten Download der Datei wichtig. Bei wiederholten Besuchen wird die Datei aus dem Cache übermittelt, und es tritt kein Problem mit der Codeübermittlung auf.

Hier kommen wir zu Grund Nummer zwei, der bald zum Hauptproblem großer Webanwendungen werden kann oder bereits geworden ist. Viele haben bereits geraten: Ja, wir sprechen über die Dauer des Parsens.

Moderne Engines wie V8 können Cache und das Ergebnis von Parsing, aber bisher funktioniert es nicht sehr effizient. Eddie Osmani hat einen großartigen Artikel zu diesem Thema: https://v8.dev/blog/cost-of-javascript-2019 . Sie können auch den V8-Blog abonnieren: https://twitter.com/v8js .

Es ist die Dauer des Parsens, die wir erheblich reduzieren werden. Dies ist besonders wichtig für mobile Geräte mit schwachen Prozessoren .

In Anwendungspaketen @ youla-web / app- * ist die Entwicklung eher „geschäftsorientiert“. Aus Gründen der Geschwindigkeit / Einfachheit / persönlichen Vorlieben wird die Entscheidung getroffen, die der Entwickler selbst in dieser Situation für am richtigsten hält. Es kommt häufig vor, dass es bei der Entwicklung kleiner MVP-Funktionen besser ist, zuerst eine einfachere und schnellere Version (<ComponentB />) zu schreiben. In einer solchen Komponente gibt es die Hälfte der Zeilen. Und wie wir wissen, je mehr Code - desto mehr Fehler.

Nach Überprüfung der Relevanz der Funktion kann die Komponente bei Bedarf durch eine optimierte und produktivere Version <ComponentA /> ersetzt werden.

Ich rate Ihnen auch, einen Blick auf die Komponente zu werfen. Wenn die Benutzeroberflächen der mobilen und der Desktop-Version sehr unterschiedlich sind, sollten sie möglicherweise getrennt werden, wobei eine gemeinsame Logik an einem Ort verbleibt. Auf diese Weise können Sie die Schmerzen beim Schreiben von komplexem CSS und Probleme mit Fehlern in einer der Anzeigen beim Umgestalten oder Ändern einer anderen beseitigen. Und umgekehrt, wenn die Benutzeroberfläche so nah wie möglich ist, warum dann die zusätzliche Arbeit?

Fazit


Zusammenfassen. Wir haben die Terminologie der adaptiven / responsiven Schnittstelle verstanden, verschiedene Möglichkeiten zur Bestimmung der Mobilität und verschiedene Optionen zum Organisieren der Codestruktur der adaptiven Komponente untersucht und die Vor- und Nachteile der einzelnen Komponenten identifiziert. Sicherlich war Ihnen vieles davon bereits bekannt, aber Wiederholung ist der beste Weg, um sich zu konsolidieren. Ich hoffe du hast etwas Neues für dich gelernt. Das nächste Mal möchten wir eine Sammlung von Empfehlungen zum Schreiben progressiver Webanwendungen mit Tipps zum Organisieren, Wiederverwenden und Verwalten von Code veröffentlichen.

All Articles