Уравнения Френеля описывают отражение света при инциденте на границе раздела между различными оптическими носителями.
- https://en.wikipedia.org/wiki/fresnel_equations
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6Оглавление
При написании отзывчивых компонентов часто используют медиа -запросы для регулировки дисплея при выполнении определенных условий. Исторически это происходило непосредственно в 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 " /> Подключившись к определению точки останова, @artsy/fresnel использует этот декларативный подход и вносит его в мир реагирования.
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" ) ) Первая важная вещь, которую следует отметить, заключается в том, что при Рендеринге сервера с помощью @artsy/fresnel все точки останова получаются на сервере. Каждый компонент Media обертывается простым CSS, который покажет эту точку останова, только если он соответствует текущему размеру браузера пользователя. Это означает, что клиент может точно начать рендеринг HTML/CSS, пока он получает разметку, что задолго до того, как приложение React загрузилось. Это улучшает воспринимаемую производительность для конечных пользователей.
Почему бы просто не отобразить тот, который нужен текущему устройству? Мы не можем точно определить, какая точка останова нуждается на вашем устройстве на сервере. Мы могли бы использовать библиотеку, чтобы понюхать пользовательский агент браузера, но они не всегда точны, и они не дадут нам всю информацию, которую мы должны знать, когда мы находимся на сервере. Как только клиентские ботинки JS и реагируют прикрепления, он просто промывает DOM и удаляет разметку, которая не требуется, через вызов matchMedia .
Во -первых, настроить @artsy/fresnel в Media -файле, который можно поделиться через приложение:
// 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 Создайте новый файл App , который станет точкой запуска для нашего приложения:
// 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 >
)
} Mount <App /> на клиенте:
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) Затем на сервере настройте рендеринг SSR и передайте mediaStyle в тег <style> в заголовке:
// 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" )
} )И это все! Чтобы проверить, отключить JS и масштабировать окно вашего браузера до мобильного размера и перезагрузить; Он правильно отодвинет мобильный макет без необходимости использовать пользовательский агент или другие «намеки на стороне сервера».
@artsy/fresnel отлично работает с статическим гибридным подходом Gatsby или Next.js к рендерингу. См. Примеры ниже для простой реализации.
Есть четыре примера, которые можно изучить в папке /examples :
В то время как Basic и SSR -примеры получит один довольно далеко, @artsy/fresnel может сделать намного больше. Для исчерпывающего глубокого погружения в его функции ознакомьтесь с приложением кухонной раковины.
Если вы используете Gatsby, вы также можете попробовать Gatsby-Plugin-Fresnel для легкой конфигурации.
Другие существующие решения используют условно отображаемый подход, такой как react-responsive или react-media , так где этот подход отличается?
Рендеринг на стороне сервера!
Но во -первых, что такое условный рендеринг?
В экосистеме React Общий подход к написанию декларативных отзывчивых компонентов заключается в использовании API Browser matchMedia :
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >На клиенте, когда данная точка останова сопоставлена, реагируйте условное, отдает дерево.
Тем не менее, этот подход имеет некоторые ограничения для того, чего мы хотели достичь при настройке рендеринга на стороне сервера:
Невозможно надежно знать текущую точку останова пользователя на этапе рендеринга сервера, поскольку для этого требуется браузер.
Установка размеров точек останова на основе нюхания пользовательского агента подвержена ошибкам из-за неспособности точно соответствовать возможностям устройства с размером. Одно мобильное устройство может иметь большую плотность пикселей, чем другое, мобильное устройство может соответствовать нескольким точкам останова при принятии во внимание ориентацию устройства, а на рабочем столе нет никакого способа узнать. Лучшие разработчики могут сделать, угадайте текущую точку останова и заполняет <Responsive> с предполагаемым состоянием.
Вытяжка остановилась на том, что, по нашему мнению, делает лучшие компромиссы. Мы подходим к этой проблеме следующим образом:
Рендер размещена для всех точек останова на сервере и отправьте его вниз по проволоку.
Браузер получает разметку с надлежащим стилем медиа -запросов и немедленно начнет отдавать ожидаемый визуальный результат для любой ширины просмотра, в котором находится браузер.
Когда All JS загрузил и React запускает фазу регидратации, мы запросим браузер на то, в какой точке перерыва он в настоящее время находится, а затем ограничиваем отображаемые компоненты подходящими средствами массовой информации. Это предотвращает выстрел в жизненный цикл в скрытых компонентах и неиспользованном HTML, который переписывается в DOM.
Кроме того, мы регистрируем слушателей событий в браузере, чтобы уведомить MediaContextProvider , когда совпадает другая точка останова, а затем повторно рендеринг дерева, используя новое значение для onlyMatch .
Давайте сравним, как будет выглядеть дерево компонентов с использованием matchMedia с нашим подходом:
| До | После |
|---|---|
< Responsive >
{ ( { sm } ) => {
if ( sm ) return < SmallArticleItem { ... props } />
else return < LargeArticleItem { ... props } />
} }
</ Responsive > | < >
< Media at = "sm" >
< SmallArticleItem { ... props } />
</ Media >
< Media greaterThan = "sm" >
< LargeArticleItem { ... props } />
</ Media >
</ > |
См. Приложение рендеринга на стороне сервера для рабочего примера.
Перво -на первый взгляд. Вам нужно будет определить точки останова и взаимодействие, необходимые для вашего дизайна для создания набора компонентов мультимедиа, которые вы можете использовать на протяжении всего приложения.
Например, рассмотрим приложение, которое имеет следующие точки останова:
sm .md .lg .xl .И следующие взаимодействия:
hover .notHover .Затем вы бы создали набор медиакомпонентов, подобных S.
// 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 } = ExampleAppMediaКак вы можете видеть, точки останова определяются их началом смещением, где, как ожидается, первое запустится с 0.
Компонент MediaContextProvider влияет на то, как будут отображаться компоненты Media . Установите его в корне вашего компонентного дерева:
import React from "react"
import { MediaContextProvider } from "./Media"
export const App = ( ) => {
return < MediaContextProvider > ... </ MediaContextProvider >
} Media , созданный для вашего приложения, имеет несколько взаимоисключающих реквизитов, которые составляют API, который вы используете, чтобы объявить ваши адаптивные макеты. Все эти реквизиты работают в зависимости от названных точек останова, которые были предоставлены при создании медиа -компонентов.
import React from "react"
import { Media } from "./Media"
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ >
)
}Примеры, приведенные для каждой опоры, используют определения точки останова, как определено в вышеуказанном разделе «Настройка».
Если вы хотите избежать основного Div, который генерируется <Media> , и вместо этого используйте свой собственный элемент, используйте форму рендеринга-пропак
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 >
</ >
)
} Примечание: это используется только при рендеринге SSR
Помимо компонентов Media и MediaContextProvider , существует функция createMediaStyle , которая производит стиль CSS для всех возможных запросов на медиа, которые может использовать экземпляр Media , в то время как разметка передается с сервера к клиенту во время гидратации. Если используется только подмножество ключей точки останова, они могут быть необязательно указаны в качестве параметра для минимизации выхода. Обязательно вставьте это в тег <style> в вашем документе <head> .
Рекомендуется сделать эту настройку в своем собственном модуле, чтобы ее можно было легко импортировать на протяжении всего вашего приложения:
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 Рендеринг может быть ограничен конкретными точками/взаимодействиями, указав список запросов на медиа. По умолчанию все будут отображаться.
По умолчанию, когда он отображается на стороне клиента, API Browser matchMedia будет использоваться для дальнейшего ограничения списка onlyMatch только в соответствии с соответствующими медиа-запросами. Это сделано, чтобы избежать запуска крючков жизненного цикла, связанных с креплением, скрытых компонентов.
Отключение этого поведения в основном предназначено для отладки.
Используйте это, чтобы заявить, что дети должны быть видны только в определенной точке останова, что означает, что ширина просмотра больше или равна смещению начала точки останова, но меньше, чем следующая точка останова, если он существует.
Например, дети этого декларации Media будут видны только в том случае, если ширина просмотра составляет от 0 до 768 (768 не включенных) баллов:
< Media at = "sm" > ... </ Media >Соответствующее правило CSS:
@media not all and ( min-width : 0 px ) and ( max-width : 767 px ) {
. fresnel-at-sm {
display : none !important ;
}
}Используйте это, чтобы заявить, что дети должны быть видны только в то время как ширина просмотра меньше, чем начало смещения указанной точки останова.
Например, дети этого декларации Media будут видны только в том случае, если ширина просмотра составляет от 0 до 1024 (1024 не включено) баллы:
< Media lessThan = "lg" > ... </ Media >Соответствующее правило CSS:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}Используйте это, чтобы заявить, что дети должны быть видны только в то время как ширина просмотра равна или больше, чем смещение начала следующей точки останова.
Например, дети этого декларации Media будут видны только в том случае, если ширина просмотра равна или превышает 1024 балла:
< Media greaterThan = "md" > ... </ Media >Соответствующее правило CSS:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}Используйте это, чтобы заявить, что дети должны быть видны только в то время, когда ширина просмотра равен испуску указанной точки останова или больше.
Например, дети этого декларации Media будут видны только в случае ширины просмотра 768 баллов или выше:
< Media greaterThanOrEqual = "md" > ... </ Media >Соответствующее правило CSS:
@media not all and ( min-width : 768 px ) {
. fresnel-greaterThanOrEqual-md {
display : none !important ;
}
}Используйте это, чтобы заявить, что дети должны быть видны только в то время, когда ширина просмотра равен началу смещения первой указанной точки останова, но меньше, чем смещение начала второй указанной точки останова.
Например, дети этого декларации Media будут видны только в том случае, если ширина просмотра составляет от 768 до 1192 (1192 не включенных) баллов:
< Media between = { [ "md" , "xl" ] } > ... </ Media >Соответствующее правило CSS:
@media not all and ( min-width : 768 px ) and ( max-width : 1191 px ) {
. fresnel-between-md-xl {
display : none !important ;
}
}Плюсы:
Минусы:
<Media> , в котором они оказываются. Этот последний пункт представляет интересную проблему. Как мы можем представлять компонент, который по -разному стилизуется в разных точках отдыха? (Давайте представим пример 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 } />
}Мы все еще выясняем для этого шаблоны, поэтому, пожалуйста, дайте нам знать, если у вас есть предложения.
Этот проект использует автоматический релиз для автоматического выпуска на каждом PR. У каждого PR должен быть этикетка, которая соответствует одному из следующих
Major, Minor и Patch приведет к созданию нового релиза. Используйте Major для нарушения изменений, несовершеннолетних для новых неразборчивых функций и исправления для исправлений ошибок. Trivial не вызовет релиз и должен использоваться при обновлении документации или непроектного кода.
Если вы не хотите выпускать на определенном PR, но изменения не тривиальны, используйте тег Skip Release вместе на стороне соответствующей теги версии.