Die Fresnel -Gleichungen beschreiben die Reflexion von Licht, wenn sie auf eine Schnittstelle zwischen verschiedenen optischen Medien fällt.
- https://en.wikipedia.org/wiki/fresnel_equations
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6Inhaltsverzeichnis
Beim Schreiben von reaktionsschnellen Komponenten ist es üblich, Medienabfragen zu verwenden, um die Anzeige anzupassen, wenn bestimmte Bedingungen erfüllt sind. Historisch gesehen hat dies direkt in CSS/HTML stattgefunden:
@media screen and ( max-width : 767 px ) {
. my-container {
width : 100 % ;
}
}
@media screen and ( min-width : 768 px ) {
. my-container {
width : 50 % ;
}
} < div class =" my-container " /> Indem @artsy/fresnel eine Breakpoint -Definition verfolgt, verfolgt er diesen deklarativen Ansatz und bringt ihn in die React -Welt.
import React from "react"
import ReactDOM from "react-dom"
import { createMedia } from "@artsy/fresnel"
const { MediaContextProvider , Media } = createMedia ( {
// breakpoints values can be either strings or integers
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
const App = ( ) => (
< MediaContextProvider >
< Media at = "sm" >
< MobileApp />
</ Media >
< Media at = "md" >
< TabletApp />
</ Media >
< Media greaterThanOrEqual = "lg" >
< DesktopApp />
</ Media >
</ MediaContextProvider >
)
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) Das erste wichtige, was zu beachten ist, ist, dass beim Server-Rendering mit @artsy/fresnel alle Haltepunkte vom Server gerendert werden. Jede Media wird von Plain CSS eingepackt, die nur diesen Haltepunkt zeigt, wenn sie mit der aktuellen Browsergröße des Benutzers übereinstimmt. Dies bedeutet, dass der Client genau damit beginnen kann, die HTML/CSS zu rendern, während er das Markup erhält, der lange bevor die Reaktionsanwendung gestartet wurde. Dies verbessert die wahrgenommene Leistung für Endbenutzer.
Warum rendern Sie nicht einfach die, die das aktuelle Gerät benötigt? Wir können nicht genau identifizieren, welcher Breakpoint Ihr Gerät auf dem Server benötigt. Wir könnten eine Bibliothek verwenden, um den Browser-Benutzer-Agent zu schnüffeln, aber diese sind nicht immer korrekt, und sie würden uns nicht alle Informationen geben, die wir wissen müssen, wenn wir Server-Rendering sind. Sobald die Client-Seite-JS-Stiefel und die Reaktoren angebracht sind, wäscht es einfach über das DOM und beseitigt das nicht benötigte Markup über einen matchMedia -Aufruf.
Konfigurieren Sie zunächst @artsy/fresnel in einer Media , die in der App gemeinsam genutzt werden kann:
// Media.tsx
import { createMedia } from "@artsy/fresnel"
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia . createMediaStyle ( )
export const { Media , MediaContextProvider } = ExampleAppMedia Erstellen Sie eine neue App -Datei, die der Startpunkt für unsere Anwendung ist:
// App.tsx
import React from "react"
import { Media , MediaContextProvider } from "./Media"
export const App = ( ) => {
return (
< MediaContextProvider >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ MediaContextProvider >
)
} Berg <App /> im Client:
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) Setzen Sie dann auf dem Server das SSR -Rendering und geben Sie mediaStyle in ein <style> -Tag im Header ein:
// server.tsx
import React from "react"
import ReactDOMServer from "react-dom/server"
import express from "express"
import { App } from "./App"
import { mediaStyle } from "./Media"
const app = express ( )
app . get ( "/" , ( _req , res ) => {
const html = ReactDOMServer . renderToString ( < App /> )
res . send ( `
<html>
<head>
<title>@artsy/fresnel - SSR Example</title>
<!–– Inject the generated styles into the page head -->
<style type="text/css"> ${ mediaStyle } </style>
</head>
<body>
<div id="react"> ${ html } </div>
<script src='/assets/app.js'></script>
</body>
</html>
` )
} )
app . listen ( 3000 , ( ) => {
console . warn ( "nApp started at http://localhost:3000 n" )
} )Und das war's! Deaktivieren Sie JS und skalieren Sie Ihr Browserfenster auf eine mobile Größe und laden Sie nach. Es wird das mobile Layout korrekt rendern, ohne dass eine Benutzer-Agent oder andere "Hinweise" für serverseitig verwendet werden müssen.
@artsy/fresnel arbeitet hervorragend mit Gatsby oder Next.js 'statischer Hybridansatz für das Rendering. In den folgenden Beispielen finden Sie eine einfache Implementierung.
Es gibt vier Beispiele, die man im Ordner /examples untersuchen kann:
Während die Basic und SSR -Beispiele einen ziemlich weit bringen werden, kann @artsy/fresnel viel mehr tun. Für ein erschöpfendes Tiefen-Tauchgang in seine Funktionen finden Sie die App der Küchenspüle.
Wenn Sie Gatsby verwenden, können Sie auch Gatsby-Plugin-Fresnel für eine einfache Konfiguration ausprobieren.
Andere vorhandene Lösungen verfolgen einen konditionell gerenderten Ansatz, wie z. B. react-responsive oder react-media . Wo unterscheidet sich dieser Ansatz also?
Serverseite Rendering!
Aber zuerst, was ist das bedingte Rendering?
Im React -Ökosystem besteht ein häufiger Ansatz zum Schreiben deklarativer reaktionsschneller Komponenten darin, die matchMedia -API des Browsers zu verwenden:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >Wenn ein bestimmter Breakpoint übereinstimmt, reagiert reagiert auf dem Client bedingt ein Baum.
Dieser Ansatz hat jedoch einige Einschränkungen für das, was wir mit unserem serverseitigen Rendering-Setup erreichen wollten:
Es ist unmöglich, den aktuellen Haltepunkt des Benutzers während der Server -Render -Phase zuverlässig zu kennen, da dies einen Browser erfordert.
Das Einstellen von Haltepunktgrößen basierend auf dem Sniffing von Benutzern-Agent ist aufgrund der Unfähigkeit, die Gerätefunktionen genau mit der Größe übereinzustimmen, zu Fehlern. Ein mobiles Gerät hat möglicherweise eine größere Pixeldichte als ein anderes. Ein mobiles Gerät passt möglicherweise zu mehreren Haltepunkten, wenn die Geräteorientierung berücksichtigt wird, und auf Desktop -Clients gibt es überhaupt keine Möglichkeit, überhaupt zu wissen. Die besten Entwickler können den aktuellen Breakpoint erraten und <Responsive> mit angenommenem Zustand bevölkern.
Artsy entschied sich für das, was wir glauben, die besten Kompromisse. Wir gehen dieses Problem auf folgende Weise an:
Rendern Sie das Markup für alle Haltepunkte auf dem Server und senden Sie ihn den Kabel ab.
Der Browser erhält Markup mit dem richtigen Medienfragestyling und wird sofort das erwartete visuelle Ergebnis für die Ansichtsfensterbreite, in der sich der Browser befindet, sofort erzielt.
Wenn alle JS geladen und React die Rehydrationsphase startet, fragen wir den Browser nach dem Breakpoint, in dem sie sich derzeit befindet, und beschränken dann die gerenderten Komponenten auf die passenden Medienabfragen. Dies verhindert, dass Lebenszyklusmethoden in verborgenen Komponenten abgefeuert werden, und nicht verwendete HTML wird in das DOM umgeschrieben.
Darüber hinaus registrieren wir Event-Hörer beim Browser, um den MediaContextProvider zu informieren, wenn ein anderer Haltepunkt übereinstimmt, und dann den Baum mit dem neuen Wert für die onlyMatch Requisite neu zu rendern.
Vergleichen wir, wie ein Komponentenbaum mit matchMedia mit unserem Ansatz aussehen würde:
| Vor | Nach |
|---|---|
< Responsive >
{ ( { sm } ) => {
if ( sm ) return < SmallArticleItem { ... props } />
else return < LargeArticleItem { ... props } />
} }
</ Responsive > | < >
< Media at = "sm" >
< SmallArticleItem { ... props } />
</ Media >
< Media greaterThan = "sm" >
< LargeArticleItem { ... props } />
</ Media >
</ > |
In der serverseitigen Rendering-App finden Sie ein Arbeitsbeispiel.
Das Wichtigste zuerst. Sie müssen die Haltepunkte und die Interaktion definieren, die für Ihr Design benötigt werden, um die Menge der Medienkomponenten zu erstellen, die Sie während Ihrer gesamten Anwendung verwenden können.
Betrachten Sie beispielsweise eine Anwendung mit den folgenden Haltepunkten:
sm .md .lg .xl .Und die folgenden Interaktionen:
hover unterstützt.notHover unterstützt.Sie würden dann den Satz von Medienkomponenten wie SO produzieren:
// Media.tsx
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
interactions : {
hover : "(hover: hover)" ,
notHover : "(hover: none)" ,
landscape : "not all and (orientation: landscape)" ,
portrait : "not all and (orientation: portrait)" ,
} ,
} )
export const { Media , MediaContextProvider , createMediaStyle } = ExampleAppMediaWie Sie sehen können, werden Breakpoints durch den Startversatz definiert, bei dem der erste bei 0 erwartet wird.
Die MediaContextProvider -Komponente beeinflusst, wie Media gerendert werden. Montieren Sie es an der Wurzel Ihres Komponentenbaums:
import React from "react"
import { MediaContextProvider } from "./Media"
export const App = ( ) => {
return < MediaContextProvider > ... </ MediaContextProvider >
} Die für Ihre Anwendung erstellte Media enthält einige gegenseitig ausschließliche Requisiten, aus denen die API besteht, mit der Sie Ihre reaktionsschnellen Layouts deklarieren. Diese Requisiten arbeiten alle basierend auf den benannten Haltepunkten, die bei der Erstellung der Medienkomponenten bereitgestellt wurden.
import React from "react"
import { Media } from "./Media"
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ >
)
}Die Beispiele für jede Prop verwenden Breakpoint -Definitionen, wie im obigen Abschnitt "Setup" definiert.
Wenn Sie das von <Media> generierte DIV vermeiden und stattdessen Ihr eigenes Element verwenden möchten, verwenden Sie das Formular für Render-Props, stellen Sie jedoch sicher, dass Sie Kinder nicht ergeben, wenn dies nicht erforderlich ist:
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" >
{ ( className , renderChildren ) => {
return (
< MySpecialComponent className = { className } >
{ renderChildren ? "Hello desktop!" : null }
</ MySpecialComponent >
)
} }
</ Media >
</ >
)
} Hinweis: Dies wird nur beim SSR -Rendering verwendet
Neben den Komponenten Media und MediaContextProvider gibt es eine createMediaStyle -Funktion, die das CSS -Styling für alle möglichen Medienabfragen erzeugt, die die Media während der Flüssigkeitszufuhr vom Server an den Client übergeben kann. Wenn nur eine Teilmenge von Haltepunkttasten verwendet wird, können diese als Parameter optional angegeben werden, um den Ausgang zu minimieren. Stellen Sie sicher, dass Sie dies in ein <style> -Tag in das <head> Ihres Dokuments einfügen.
Es ist ratsam, dieses Setup in seinem eigenen Modul durchzuführen, damit sie während Ihrer Bewerbung problemlos importiert werden können:
import { createMedia } from "@artsy/fresnel"
const ExampleAppMedia = createMedia ( {
breakpoints : {
sm : 0 ,
md : 768 ,
lg : 1024 ,
xl : 1192 ,
} ,
} )
// Generate CSS to be injected into the head
export const mediaStyle = ExampleAppMedia . createMediaStyle ( ) // optional: .createMediaStyle(['at'])
export const { Media , MediaContextProvider } = ExampleAppMedia Das Rendering kann auf bestimmte Haltepunkte/Interaktionen beschränkt werden, indem eine Liste der übereinstimmenden Medienabfragen angegeben wird. Standardmäßig werden alle alle gerendert.
Standardmäßig wird die matchMedia API des Browsers, wenn sie die Client-Seite rendert, die onlyMatch Liste der einzigen Medien weiterhin auf die derzeit entsprechenden Medienabfragen einschränken. Dies geschieht, um zu vermeiden, dass die montierenden Lebenszyklushaken versteckter Komponenten ausgelöst werden.
Das Deaktivieren dieses Verhaltens ist hauptsächlich für Debugging -Zwecke bestimmt.
Verwenden Sie dies, um zu erklären, dass Kinder nur an einem bestimmten Haltepunkt sichtbar sein sollten, was bedeutet, dass die Ansichtsfensterbreite größer oder gleich dem Startversatz des Haltepunkts ist, jedoch weniger als der nächste Haltepunkt, wenn einer existiert.
Beispielsweise sind Kinder dieser Media nur dann sichtbar, wenn die Ansichtsfenster zwischen 0 und 768 (768 nicht enthalten) Punkte liegt:
< Media at = "sm" > ... </ Media >Die entsprechende CSS -Regel:
@media not all and ( min-width : 0 px ) and ( max-width : 767 px ) {
. fresnel-at-sm {
display : none !important ;
}
}Verwenden Sie dies, um zu erklären, dass Kinder nur sichtbar sein sollten, während die Ansichtsfensterbreite geringer ist als der Startversatz des angegebenen Haltepunkts.
Beispielsweise sind Kinder dieser Media nur sichtbar, wenn die Ansichtsfensterbreite zwischen 0 und 1024 (1024 nicht enthalten) Punkte liegt:
< Media lessThan = "lg" > ... </ Media >Die entsprechende CSS -Regel:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}Verwenden Sie dies, um zu erklären, dass Kinder nur sichtbar sein sollten, während die Ansichtsfensterbreite gleich oder größer ist als der Startversatz des nächsten Haltepunkts.
Beispielsweise sind Kinder dieser Media nur dann sichtbar, wenn die Ansichtsfensterbreite gleich oder mehr als 1024 Punkte ist:
< Media greaterThan = "md" > ... </ Media >Die entsprechende CSS -Regel:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}Verwenden Sie dies, um zu erklären, dass Kinder nur sichtbar sein sollten, während die Ansichtsfensterbreite dem Startversatz des angegebenen Breakpoint oder größer ist.
Beispielsweise werden Kinder dieser Media nur dann sichtbar, wenn die Ansichtsfensterbreite 768 Punkte beträgt oder nach oben:
< Media greaterThanOrEqual = "md" > ... </ Media >Die entsprechende CSS -Regel:
@media not all and ( min-width : 768 px ) {
. fresnel-greaterThanOrEqual-md {
display : none !important ;
}
}Verwenden Sie dies, um zu erklären, dass Kinder nur sichtbar sein sollten, während die Ansichtsfensterbreite dem Startversatz des ersten angegebenen Haltepunkts entspricht, jedoch weniger als der Startversatz des zweiten angegebenen Haltepunkts.
Beispielsweise werden Kinder dieser Media nur dann sichtbar, wenn die Ansichtsfensterbreite zwischen 768 und 1192 (1192 nicht enthalten) Punkte liegt:
< Media between = { [ "md" , "xl" ] } > ... </ Media >Die entsprechende CSS -Regel:
@media not all and ( min-width : 768 px ) and ( max-width : 1191 px ) {
. fresnel-between-md-xl {
display : none !important ;
}
}Profis:
Nachteile:
<Media> medienkomponenten bestimmt, in denen sie sich befinden. Dieser letzte Punkt zeigt ein interessantes Problem. Wie können wir eine Komponente darstellen, die an verschiedenen Haltepunkten unterschiedlich gestylt wird? (Stellen wir uns ein matchMedia -Beispiel vor.)
< Sans size = { sm ? 2 : 3 } > < >
< Media at = "sm" > { this . getComponent ( "sm" ) } </ Media >
< Media greaterThan = "sm" > { this . getComponent ( ) } </ Media >
</ > getComponent ( breakpoint ?: string ) {
const sm = breakpoint === 'sm'
return < Sans size = { sm ? 2 : 3 } />
}Wir finden immer noch Muster dafür. Bitte teilen Sie uns bitte mit, ob Sie Vorschläge haben.
In diesem Projekt werden automatisch die Freisetzung automatisch auf jeder PR veröffentlicht. Jeder PR sollte ein Etikett haben, das einem der folgenden entspricht
Major, Moll und Patch führen dazu, dass eine neue Version generiert wird. Verwenden Sie Major für Breaking-Änderungen, moll für neue nicht bahnbrechende Funktionen und Patch für Fehlerbehebungen. Trivial verursacht keine Veröffentlichung und sollte bei der Aktualisierung der Dokumentation oder des Nichtprojekt-Codes verwendet werden.
Wenn Sie nicht in einer bestimmten PR veröffentlichen möchten, die Änderungen nicht trivial sind, verwenden Sie das Skip Release -Tag über die entsprechende Versions -Tag -Tag.