Les équations de Fresnel décrivent la réflexion de la lumière lorsqu'il est incident sur une interface entre différents milieux optiques.
- https://en.wikipedia.org/wiki/fresnel_equations
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6Table des matières
Lors de l'écriture de composants réactifs, il est courant d'utiliser des requêtes multimédias pour ajuster l'affichage lorsque certaines conditions sont remplies. Historiquement, cela a eu lieu directement dans CSS / HTML:
@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 " /> En se connectant à une définition de point de rupture, @artsy/fresnel adopte cette approche déclarative et l'amène dans le monde React.
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" ) ) La première chose importante à noter est que lorsque le rendu de serveur avec @artsy/fresnel , tous les points d'arrêt sont rendus par le serveur. Chaque composant Media est enveloppé par CSS ordinaire qui ne montrera ce point d'arrêt que s'il correspond à la taille actuelle du navigateur de l'utilisateur. Cela signifie que le client peut commencer avec précision à rendre le HTML / CSS pendant qu'il reçoit le balisage, qui est bien avant le démarrage de l'application React. Cela améliore les performances perçues pour les utilisateurs finaux.
Pourquoi ne pas simplement rendre celui dont l'appareil actuel a besoin? Nous ne pouvons pas identifier avec précision le point d'arrêt dont votre appareil a besoin sur le serveur. Nous pourrions utiliser une bibliothèque pour renifler l'agent utilisateur du navigateur, mais ceux-ci ne sont pas toujours exacts, et ils ne nous donneraient pas toutes les informations que nous devons savoir lorsque nous sommes des serveurs. Une fois que les bottes JS côté client et la réaction se sont fixées, il lave simplement le DOM et supprime le balisage qui est inutile, via un appel matchMedia .
Tout d'abord, configurez @artsy/fresnel dans un fichier Media qui peut être partagé sur l'application:
// 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 Créez un nouveau fichier App qui sera le point de lancement de notre application:
// 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 >
)
} Monter <App /> sur le client:
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) Ensuite, sur le serveur, configurez le rendu SSR et passe mediaStyle dans une balise <style> dans l'en-tête:
// 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" )
} )Et c'est tout! Pour tester, désactiver JS et mettre à l'échelle votre fenêtre de navigateur sur une taille mobile et un rechargement; Il rendra correctement la mise en page mobile sans avoir besoin d'utiliser un agent utilisateur ou d'autres "conseils" côté serveur.
@artsy/fresnel fonctionne très bien avec l'approche hybride statique de Gatsby ou Next.js pour le rendu. Voir les exemples ci-dessous pour une simple implémentation.
Il y a quatre exemples que l'on peut explorer dans le dossier /examples :
Bien que les exemples Basic et SSR en vont assez loin, @artsy/fresnel peut en faire beaucoup plus. Pour une plongée profonde exhaustive dans ses fonctionnalités, consultez l'application de l'évier de la cuisine.
Si vous utilisez Gatsby, vous pouvez également essayer Gatsby-Plugin-Fresnel pour une configuration facile.
D'autres solutions existantes adoptent une approche rendue conditionnelle, telle que react-responsive et react-media , alors où cette approche diffère-t-elle?
Rendu côté serveur!
Mais d'abord, qu'est-ce que le rendu conditionnel?
Dans l'écosystème React, une approche commune de l'écriture de composantes réactives déclaratives consiste à utiliser l'API matchMedia du navigateur:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >Sur le client, lorsqu'un point d'arrêt donné est apparié, réagir, réagir conditionnellement un arbre.
Cependant, cette approche a certaines limites pour ce que nous voulions réaliser avec notre configuration de rendu côté serveur:
Il est impossible de connaître de manière fiable le point d'arrêt actuel de l'utilisateur pendant la phase de rendu du serveur car cela nécessite un navigateur.
Le réglage des tailles de point d'arrêt basé sur le reniflement de l'agent utilisateur est sujet aux erreurs en raison de l'incapacité de faire correspondre précisément les capacités des périphériques à la taille. Un appareil mobile peut avoir une plus grande densité de pixels qu'un autre, un appareil mobile peut s'adapter à plusieurs points d'arrêt lors de la prise en considération de l'orientation de l'appareil, et sur les clients de bureau, il n'y a aucun moyen de savoir du tout. Les meilleurs développeurs peuvent faire est de deviner le point d'arrêt actuel et peupler <Responsive> avec un état supposé.
Artsy s'est installé sur ce que nous pensons faire les meilleurs compromis. Nous abordons ce problème de la manière suivante:
Rendez-vous pour tous les points d'arrêt sur le serveur et envoyez-le dans le fil.
Le navigateur reçoit un balisage avec un style de requête multimédia approprié et commencera immédiatement à rendre le résultat visuel attendu pour la largeur de la fenêtre de la fenêtre dans laquelle se trouve le navigateur.
Lorsque tout JS a chargé et React démarre la phase de réhydratation, nous interrogeons le navigateur pour le point d'arrêt dans lequel il se trouve actuellement, puis limitons les composants rendus aux requêtes multimédias correspondantes. Cela empêche les méthodes de cycle de vie de tirer dans des composants cachés et de réécrire le HTML inutilisé au DOM.
De plus, nous enregistrons des écouteurs d'événements avec le navigateur pour informer le MediaContextProvider lorsqu'un point d'arrêt différent est apparié, puis réintégrer l'arborescence en utilisant la nouvelle valeur pour le onlyMatch proportionnaire.
Comparons à quoi ressemblerait un arbre de composants à l'aide de matchMedia avec notre approche:
| Avant | Après |
|---|---|
< Responsive >
{ ( { sm } ) => {
if ( sm ) return < SmallArticleItem { ... props } />
else return < LargeArticleItem { ... props } />
} }
</ Responsive > | < >
< Media at = "sm" >
< SmallArticleItem { ... props } />
</ Media >
< Media greaterThan = "sm" >
< LargeArticleItem { ... props } />
</ Media >
</ > |
Voir l'application de rendu côté serveur pour un exemple de travail.
Tout d'abord. Vous devrez définir les points d'arrêt et l'interaction nécessaires pour que votre conception produise l'ensemble des composants multimédias que vous pouvez utiliser tout au long de votre application.
Par exemple, considérez une application qui a les points d'arrêt suivants:
sm .md .lg .xl .Et les interactions suivantes:
hover .notHover .Vous produiseriez ensuite l'ensemble des composants multimédias comme tel:
// 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 } = ExampleAppMediaComme vous pouvez le voir, les points d'arrêt sont définis par leur décalage de début , où le premier devrait commencer à 0.
Le composant MediaContextProvider influence la façon dont les composants Media seront rendus. Montez-le à la racine de votre arbre de composant:
import React from "react"
import { MediaContextProvider } from "./Media"
export const App = ( ) => {
return < MediaContextProvider > ... </ MediaContextProvider >
} Le composant Media créé pour votre application a quelques accessoires mutuellement exclusifs qui composent l'API que vous utilisez pour déclarer vos dispositions réactives. Ces accessoires fonctionnent tous en fonction des points d'arrêt nommés qui ont été fournis lorsque vous avez créé les composants multimédias.
import React from "react"
import { Media } from "./Media"
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ >
)
}Les exemples donnés pour chaque accessoire utilisent des définitions de points d'arrêt tels que définis dans la section «Configuration» ci-dessus.
Si vous souhaitez éviter la div sous-jacente générée par <Media> et utilisez plutôt votre propre élément, utilisez le formulaire Render Props, mais assurez-vous de ne rendre aucun enfant lorsque cela n'est pas nécessaire:
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 >
</ >
)
} Remarque: Ceci n'est utilisé que lorsque le rendu SSR
Outre les composants Media et MediaContextProvider , il existe une fonction createMediaStyle qui produit le style CSS pour toutes les requêtes multimédias possibles que l'instance Media peut utiliser pendant que le balisage est passé du serveur au client pendant l'hydratation. Si seul un sous-ensemble de touches de point d'arrêt est utilisé, ceux-ci peuvent être facultatifs spécifiés comme paramètre pour minimiser la sortie. Assurez-vous d'insérer cela dans une balise <style> dans <head> de votre document.
Il est conseillé de faire cette configuration dans son propre module afin qu'il puisse être facilement importé dans votre application:
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 Le rendu peut être limité à des points d'arrêt spécifiques / interactions en spécifiant une liste de requêtes multimédias à correspondre. Par défaut, tous seront rendus.
Par défaut, lorsqu'il est rendu côté client, l'API matchMedia du navigateur sera utilisée pour limiter davantage la liste onlyMatch pour uniquement les requêtes multimédias correspondantes. Ceci est fait pour éviter de déclencher des crochets de cycle de vie liés au montage de composants cachés.
La désactivation de ce comportement est principalement destinée à des fins de débogage.
Utilisez-le pour déclarer que les enfants ne doivent être visibles qu'à un point d'arrêt spécifique, ce qui signifie que la largeur de la fenêtre est supérieure ou égale au décalage de début du point d'arrêt, mais moins que le point d'arrêt suivant, s'il existe.
Par exemple, les enfants de cette déclaration Media ne seront visibles que si la largeur de la fenêtre se situe entre 0 et 768 (768 non incluses):
< Media at = "sm" > ... </ Media >La règle CSS correspondante:
@media not all and ( min-width : 0 px ) and ( max-width : 767 px ) {
. fresnel-at-sm {
display : none !important ;
}
}Utilisez-le pour déclarer que les enfants ne doivent être visibles que tandis que la largeur de la fenêtre est inférieure au décalage de début du point d'arrêt spécifié.
Par exemple, les enfants de cette déclaration Media ne seront visibles que si la largeur de la fenêtre se situe entre 0 et 1024 (1024 non incluses):
< Media lessThan = "lg" > ... </ Media >La règle CSS correspondante:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}Utilisez-le pour déclarer que les enfants ne doivent être visibles que tandis que la largeur de la fenêtre est égale ou supérieure au décalage de début du prochain point d'arrêt.
Par exemple, les enfants de cette déclaration Media ne seront visibles que si la largeur de la fenêtre est égale ou supérieure à 1024 points:
< Media greaterThan = "md" > ... </ Media >La règle CSS correspondante:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}Utilisez-le pour déclarer que les enfants ne doivent être visibles que tandis que la largeur de la fenêtre est égale au décalage de début du point d'arrêt spécifié ou plus.
Par exemple, les enfants de cette déclaration Media ne seront visibles que si la largeur de la fenêtre est de 768 points ou plus:
< Media greaterThanOrEqual = "md" > ... </ Media >La règle CSS correspondante:
@media not all and ( min-width : 768 px ) {
. fresnel-greaterThanOrEqual-md {
display : none !important ;
}
}Utilisez-le pour déclarer que les enfants ne doivent être visibles que tandis que la largeur de la fenêtre est égale au décalage de début du premier point d'arrêt spécifié mais inférieur au décalage de début du deuxième point d'arrêt spécifié.
Par exemple, les enfants de cette déclaration Media ne seront visibles que si la largeur de la fenêtre se situe entre 768 et 1192 (1192 non incluses):
< Media between = { [ "md" , "xl" ] } > ... </ Media >La règle CSS correspondante:
@media not all and ( min-width : 768 px ) and ( max-width : 1191 px ) {
. fresnel-between-md-xl {
display : none !important ;
}
}Avantages:
Inconvénients:
<Media> dans laquelle ils se retrouvent. Ce dernier point présente un problème intéressant. Comment représenter un composant qui est stylé différemment à différents points d'arrêt? (Imaginons un exemple matchMedia .)
< 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 } />
}Nous sommes toujours en train de trouver des modèles pour cela, alors faites-nous savoir si vous avez des suggestions.
Ce projet utilise la libération automatique pour libérer automatiquement sur chaque PR. Chaque PR doit avoir une étiquette qui correspond à l'un des éléments suivants
Le major, le mineur et le patch entraîneront une nouvelle version. Utilisez Major pour briser les changements, mineur pour les nouvelles fonctionnalités non révolutionnaires et le correctif pour les corrections de bogues. Trivial ne provoquera pas de version et doit être utilisé lors de la mise à jour de la documentation ou du code non projeté.
Si vous ne souhaitez pas sortir sur un PR particulier, mais que les modifications ne sont pas triviales, utilisez la balise de Skip Release à côté de la balise de version appropriée.