フレネル方程式は、異なる光学媒体間の界面に入射するときの光の反射を表します。
- 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によって包まれています。これは、クライアントがMARKUPを受信している間に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フォルダーで探索できる4つの例があります。
Basic例とSSR例はかなり遠くになりますが、 @artsy/fresnelもっと多くのことをすることができます。その機能を徹底的に掘り下げるには、Kitchen Sinkアプリをご覧ください。
Gatsbyを使用している場合は、簡単な構成のためにGatsby-Plugin-Fresnelを試すこともできます。
他の既存のソリューションは、 react-responsiveやreact-mediaなど、条件付きでレンダリングされたアプローチを採用しています。このアプローチはどこで異なりますか?
サーバーサイドレンダリング!
しかし、最初に、条件付きレンダリングとは何ですか?
Reactエコシステムでは、宣言的応答性コンポーネントを作成するための一般的なアプローチは、ブラウザのmatchMedia APIを使用することです。
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >クライアントでは、特定のブレークポイントが一致すると、反応が条件付きでツリーをレンダリングします。
ただし、このアプローチには、サーバー側のレンダリングセットアップで達成したいことにいくつかの制限があります。
ブラウザが必要なため、サーバーレンダリングフェーズ中にユーザーの現在のブレークポイントを確実に知ることは不可能です。
ユーザーエージェントのスニッフィングに基づくブレークポイントサイズの設定は、デバイス機能を正確にサイズに一致させることができないため、エラーが発生しやすいです。 1つのモバイルデバイスは、別のモバイルデバイスよりもピクセル密度が大きくなる可能性があります。モバイルデバイスは、デバイスの向きを考慮に入れるときに複数のブレークポイントに適合する場合があり、デスクトップクライアントにはまったく知る方法がありません。最良の開発者ができることは、現在のブレークポイントを推測し、 <Responsive>想定状態に埋めることです。
Artsyは、私たちが考えるものに落ち着きました。この問題に次のようにアプローチします。
サーバー上のすべてのブレークポイントのマークアップをレンダリングし、ワイヤーに送信します。
ブラウザは、適切なメディアクエリスタイリングを備えたマークアップを受信し、ブラウザがあるビューポート幅に対して予想される視覚的結果のレンダリングをすぐに開始します。
すべてのJSがロードされ、反応が再水和フェーズを開始すると、現在のブレークポイントをブラウザに照会し、レンダリングされたコンポーネントを一致するメディアクエリに制限します。これにより、ライフサイクルの方法が隠されたコンポーネントで発火し、未使用の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という名前の768から1024(1024は含まれていない)ポイントの間のビューポート幅(1024は含まれていません)。lgという名前の1024から1192(1192が含まれていない)ポイントの間のビューポート幅(1192は含まれていません)。xlという名前の1192ポイント以上からのビューポート幅。と次の相互作用:
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 >
</ >
)
}上記の「セットアップ」セクションで定義されている各プロップに与えられた例は、ブレークポイント定義を使用します。
<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コンポーネントのほかに、 Mediaインスタンスが使用できるすべての可能なメディアクエリに対してCSSスタイリングを生成するcreateMediaStyle関数があります。ブレークポイントキーのサブセットのみを使用する場合、それらは出力を最小限に抑えるためにパラメーターとして指定されます。ドキュメントの<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 ;
}
}これを使用して、ビューポートの幅が指定されたブレークポイントの開始オフセットよりも少ない場合にのみ、子供が表示されるべきであると宣言します。
たとえば、この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 ;
}
}これを使用して、子供は最初の指定されたブレークポイントの開始オフセットに等しいが、2番目の指定されたブレークポイントの開始オフセットよりも少ない場合にのみ、子供が表示されるべきであると宣言します。
たとえば、この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には、次のいずれかに一致するラベルが必要です
メジャー、マイナー、およびパッチにより、新しいリリースが生成されます。変更にはメジャーを使用し、新しい非壊れない機能にはマイナー、バグ修正のパッチを使用します。 Trivialはリリースを引き起こさず、ドキュメントまたは非プロジェクトコードを更新するときに使用する必要があります。
特定のPRでリリースしたくないが、変更が簡単ではない場合は、適切なバージョンタグに沿ってSkip Releaseタグを使用してください。