菲涅爾方程描述了在不同光學介質之間界面上的光的反射。
- 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採用了這種聲明的方法,並將其帶入了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" ) ) 要注意的第一個重要的事情是,當使用@artsy/fresnel進行服務器渲染時,所有斷點都會由服務器渲染。每個Media組件都由普通CSS包裹,只有與用戶的當前瀏覽器大小匹配時,才會顯示該斷點。這意味著客戶端可以在收到標記時準確地開始渲染HTML/CSS,這是在React應用程序啟動之前很長時間。這改善了最終用戶的感知性能。
為什麼不只是渲染當前設備需要的呢?我們無法準確確定您在服務器上設備需求的斷點。我們可以使用庫來嗅探瀏覽器用戶代理,但是這些並不總是準確的,並且在服務器渲染時,它們不會為我們提供所有我們需要知道的信息。一旦客戶端的JS靴子並附加了反應,它就可以通過matchMedia調用在DOM上洗滌並刪除不需要的標記即可。
首先,在可以通過應用程序共享的Media文件中配置@artsy/fresnel :
// 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 >
)
}在客戶端上安裝<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生態系統中,編寫聲明性響應組件的一種常見方法是使用瀏覽器的matchMedia API:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >在客戶端上,當給定的斷點匹配時,有條件地呈現一棵樹。
但是,這種方法對我們想要實現的服務器端渲染設置有一些局限性:
由於需要瀏覽器,因此不可能可靠地知道用戶在服務器渲染階段的當前斷點。
基於用戶代理的嗅探設置斷點大小,由於無法將設備功能與大小匹配,因此容易出現錯誤。一個移動設備的像素密度可能比另一個設備更大,在考慮設備方向時,移動設備可能適合多個斷點,並且在桌面客戶端上根本無法知道。最好的開發人員可以做到的是猜測當前的斷點,並以假定狀態填充<Responsive>式>。
Artsy確定了我們認為是最好的權衡。我們以以下方式解決此問題:
為服務器上的所有斷點渲染標記並將其發送到電線。
瀏覽器以適當的媒體查詢樣式接收標記,並將立即開始對瀏覽器所處的視口寬度的預期視覺結果呈現。
當所有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 。然後,您將生成類似的媒體組件集:
// 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 >
</ >
)
}在上述“設置”部分中定義的每個Prop使用斷點定義給出的示例。
如果您想避免由<Media>生成的基礎DIV並使用您自己的元素,請使用渲染 - 繪畫表格,但請確保在不需要時不要渲染任何孩子:
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實例可以利用媒體實例可以利用這些查詢。如果僅使用斷點鍵的子集,則可以將其指定為最小化輸出的參數。確保將其插入文檔的<head>中的<style>標籤中。
建議在自己的模塊中進行此設置,以便在您的整個應用程序中都可以輕鬆導入:
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 通過指定要匹配的媒體查詢列表,可以將渲染限制為特定的斷點/交互。默認情況下,所有內容都將被渲染。
默認情況下,當渲染客戶端時,將使用瀏覽器的matchMedia API將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 ;
}
}用它來宣布只有在視口寬度小於指定斷點的開始偏移時才能看到兒童。
例如,只有當視口寬度在0到1024(不包括1024)點時,該Media聲明的兒童才能看到:
< Media lessThan = "lg" > ... </ Media >相應的CSS規則:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}使用它來聲明只有在視口寬度等於或大於下一個斷點的開始偏移時才能看到兒童。
例如,只有在視口寬度等於或大於1024分的情況下,該Media聲明的兒童才能看到:
< Media greaterThan = "md" > ... </ Media >相應的CSS規則:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}使用它來聲明只有在視口寬度等於指定斷點或更大的開始偏移時才能看到兒童。
例如,此Media聲明的孩子只有在視口寬度為768點或UP時才能看到:
< 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上釋放。每個公關都應具有與以下一個相匹配的標籤
專業,次要和補丁將導致生成新的版本。使用專業進行破壞變化,用於新的非破壞功能的次要變化以及用於錯誤修復的補丁。 Trivial不會引起釋放,並且在更新文檔或非項目代碼時應使用。
如果您不想在特定的PR上釋放,但更改並不是微不足道,請在適當版本標籤的側面使用Skip Release標籤。