Persamaan Fresnel menggambarkan refleksi cahaya ketika insiden pada antarmuka antara media optik yang berbeda.
- https://en.wikipedia.org/wiki/Fresnel_EQUATION
# React 18+
yarn add @artsy/fresnel
# React 17
yarn add @artsy/fresnel@6Daftar isi
Saat menulis komponen responsif, biasa menggunakan kueri media untuk menyesuaikan tampilan saat kondisi tertentu dipenuhi. Secara historis ini telah terjadi langsung di 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 " /> Dengan mengaitkan definisi breakpoint, @artsy/fresnel mengambil pendekatan deklaratif ini dan membawanya ke dunia 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" ) ) Hal penting pertama yang perlu diperhatikan adalah bahwa ketika server-rendering dengan @artsy/fresnel , semua breakpoint diterjemahkan oleh server. Setiap komponen Media dibungkus oleh CSS biasa yang hanya akan menunjukkan breakpoint jika cocok dengan ukuran browser pengguna saat ini. Ini berarti bahwa klien dapat secara akurat mulai membuat HTML/CSS saat menerima markup, yang jauh sebelum aplikasi React boot. Ini meningkatkan kinerja yang dirasakan untuk pengguna akhir.
Mengapa tidak hanya membuat yang dibutuhkan perangkat saat ini? Kami tidak dapat secara akurat mengidentifikasi breakpoint yang dibutuhkan perangkat Anda di server. Kami dapat menggunakan perpustakaan untuk mengendus-nahan agen pengguna browser, tetapi itu tidak selalu akurat, dan mereka tidak akan memberi kami semua informasi yang perlu kami ketahui ketika kami melakukan server-rendering. Setelah sepatu bot JS sisi klien dan bereaksi terpasang, itu hanya mencuci DOM dan menghilangkan markup yang tidak dibutuhkan, melalui panggilan matchMedia .
Pertama, konfigurasikan @artsy/fresnel dalam file Media yang dapat dibagikan di seluruh aplikasi:
// 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 Buat file App baru yang akan menjadi titik peluncuran untuk aplikasi kami:
// 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 /> pada klien:
// client.tsx
import React from "react"
import ReactDOM from "react-dom"
import { App } from "./App"
ReactDOM . render ( < App /> , document . getElementById ( "react" ) ) Kemudian di server, atur rendering SSR dan lewati mediaStyle ke dalam tag <style> di header:
// 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" )
} )Dan itu saja! Untuk menguji, nonaktifkan JS dan skala jendela browser Anda ke ukuran seluler dan muat ulang; Ini akan dengan benar membuat tata letak seluler tanpa perlu menggunakan agen pengguna atau "petunjuk" sisi server lainnya.
@artsy/fresnel bekerja sangat baik dengan pendekatan hibrida statis Gatsby atau Next.js untuk rendering. Lihat contoh di bawah ini untuk implementasi sederhana.
Ada empat contoh yang dapat dijelajahi di folder /examples :
Sementara contoh Basic dan SSR akan mendapatkan satu cukup jauh, @artsy/fresnel dapat melakukan lebih banyak lagi. Untuk penyelaman dalam yang melelahkan ke dalam fitur-fiturnya, lihat aplikasi Wastafel Dapur.
Jika Anda menggunakan Gatsby, Anda juga dapat mencoba Gatsby-Plugin-Fresnel untuk konfigurasi yang mudah.
Solusi lain yang ada mengambil pendekatan yang diberikan secara kondisional, seperti react-responsive atau react-media , jadi di mana pendekatan ini berbeda?
Rendering sisi server!
Tapi pertama -tama, apa itu rendering bersyarat?
Dalam ekosistem React, pendekatan umum untuk menulis komponen responsif deklaratif adalah menggunakan API matchMedia browser:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >Pada klien, ketika breakpoint yang diberikan dicocokkan bereaksi secara kondisional membuat pohon.
Namun, pendekatan ini memiliki beberapa keterbatasan untuk apa yang ingin kami capai dengan pengaturan rendering sisi server kami:
Tidak mungkin untuk mengetahui breakpoint pengguna saat ini selama fase render server karena itu membutuhkan browser.
Mengatur ukuran breakpoint berdasarkan sniffing agen pengguna rentan terhadap kesalahan karena ketidakmampuan untuk secara tepat mencocokkan kemampuan perangkat dengan ukuran. Satu perangkat seluler mungkin memiliki kepadatan piksel yang lebih besar dari yang lain, perangkat seluler mungkin cocok dengan beberapa breakpoint saat mempertimbangkan orientasi perangkat, dan pada klien desktop tidak ada cara untuk mengetahuinya sama sekali. Devs terbaik yang dapat dilakukan adalah menebak breakpoint saat ini dan mengisi <Responsive> dengan keadaan yang diasumsikan.
Artsy memilih apa yang kita pikir membuat trade-off terbaik. Kami mendekati masalah ini dengan cara berikut:
Render markup untuk semua breakpoint di server dan kirimkan ke bawah kawat.
Browser menerima markup dengan gaya kueri media yang tepat dan akan segera mulai memberikan hasil visual yang diharapkan untuk apa pun lebar viewport apa pun browser.
Ketika semua JS telah dimuat dan bereaksi memulai fase rehidrasi, kami menanyakan browser untuk breakpoint apa saat ini dan kemudian membatasi komponen yang diberikan pada kueri media yang cocok. Ini mencegah metode siklus hidup dari penembakan dalam komponen tersembunyi dan HTML yang tidak digunakan ditulis ulang ke DOM.
Selain itu, kami mendaftarkan pendengar acara dengan browser untuk memberi tahu MediaContextProvider ketika breakpoint yang berbeda dicocokkan dan kemudian render ulang pohon menggunakan nilai baru untuk Prop onlyMatch .
Mari kita bandingkan seperti apa pohon komponen menggunakan matchMedia dengan pendekatan kami:
| Sebelum | Setelah |
|---|---|
< Responsive >
{ ( { sm } ) => {
if ( sm ) return < SmallArticleItem { ... props } />
else return < LargeArticleItem { ... props } />
} }
</ Responsive > | < >
< Media at = "sm" >
< SmallArticleItem { ... props } />
</ Media >
< Media greaterThan = "sm" >
< LargeArticleItem { ... props } />
</ Media >
</ > |
Lihat aplikasi rendering sisi server untuk contoh yang berfungsi.
Hal pertama yang pertama. Anda harus menentukan breakpoint dan interaksi yang diperlukan untuk desain Anda untuk menghasilkan serangkaian komponen media yang dapat Anda gunakan di seluruh aplikasi Anda.
Misalnya, pertimbangkan aplikasi yang memiliki breakpoint berikut:
sm .md .lg .xl .Dan interaksi berikut:
hover .notHover .Anda kemudian akan menghasilkan serangkaian komponen media seperti itu:
// 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 } = ExampleAppMediaSeperti yang Anda lihat, breakpoint ditentukan oleh start offset mereka, di mana yang pertama diharapkan akan dimulai pada 0.
Komponen MediaContextProvider mempengaruhi bagaimana komponen Media akan diberikan. Pasang pada akar pohon komponen Anda:
import React from "react"
import { MediaContextProvider } from "./Media"
export const App = ( ) => {
return < MediaContextProvider > ... </ MediaContextProvider >
} Komponen Media yang dibuat untuk aplikasi Anda memiliki beberapa alat peraga yang saling eksklusif yang membentuk API yang akan Anda gunakan untuk mendeklarasikan tata letak responsif Anda. Semua alat peraga ini beroperasi berdasarkan breakpoint bernama yang disediakan ketika Anda membuat komponen media.
import React from "react"
import { Media } from "./Media"
export const HomePage = ( ) => {
return (
< >
< Media at = "sm" > Hello mobile! </ Media >
< Media greaterThan = "sm" > Hello desktop! </ Media >
</ >
)
}Contoh yang diberikan untuk setiap prop menggunakan definisi breakpoint sebagaimana didefinisikan dalam bagian 'Pengaturan' di atas.
Jika Anda ingin menghindari div yang mendasari yang dihasilkan oleh <Media> dan alih-alih menggunakan elemen Anda sendiri, gunakan formulir render-prop tetapi pastikan untuk tidak membuat anak-anak bila tidak perlu:
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 >
</ >
)
} Catatan: Ini hanya digunakan saat rendering SSR
Selain komponen Media dan MediaContextProvider , ada fungsi createMediaStyle yang menghasilkan gaya CSS untuk semua kueri media yang mungkin yang dapat digunakan oleh contoh Media saat markup sedang diteruskan dari server ke klien selama hidrasi. Jika hanya sebagian dari tombol breakpoint yang digunakan, itu dapat opsional ditentukan sebagai parameter untuk meminimalkan output. Pastikan untuk memasukkan ini ke dalam tag <style> di <head> dokumen Anda.
Dianjurkan untuk melakukan pengaturan ini dalam modulnya sendiri sehingga dapat dengan mudah diimpor di seluruh aplikasi Anda:
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 Rendering dapat dibatasi untuk breakpoint/interaksi tertentu dengan menentukan daftar kueri media yang cocok. Secara default semua akan diterjemahkan.
Secara default, ketika disampaikan sisi klien, API matchMedia browser akan digunakan untuk lebih membatasi daftar onlyMatch ke hanya kueri media yang cocok saat ini. Hal ini dilakukan untuk menghindari pemicu kait siklus hidup terkait dari komponen tersembunyi.
Menonaktifkan perilaku ini sebagian besar dimaksudkan untuk tujuan debugging.
Gunakan ini untuk menyatakan bahwa anak -anak hanya boleh terlihat pada breakpoint tertentu, yang berarti bahwa lebar viewport lebih besar dari atau sama dengan start offset breakpoint, tetapi kurang dari breakpoint berikutnya, jika ada.
Misalnya, anak -anak dari deklarasi Media ini hanya akan terlihat jika lebar viewport adalah antara 0 dan 768 (768 tidak termasuk) poin:
< Media at = "sm" > ... </ Media >Aturan CSS yang sesuai:
@media not all and ( min-width : 0 px ) and ( max-width : 767 px ) {
. fresnel-at-sm {
display : none !important ;
}
}Gunakan ini untuk menyatakan bahwa anak -anak hanya boleh terlihat sementara lebar viewport kurang dari start offset dari breakpoint yang ditentukan.
Misalnya, anak -anak dari deklarasi Media ini hanya akan terlihat jika lebar viewport adalah antara 0 dan 1024 (1024 tidak termasuk) poin:
< Media lessThan = "lg" > ... </ Media >Aturan CSS yang sesuai:
@media not all and ( max-width : 1023 px ) {
. fresnel-lessThan-lg {
display : none !important ;
}
}Gunakan ini untuk menyatakan bahwa anak -anak hanya boleh terlihat sementara lebar viewport sama atau lebih besar dari start offset dari breakpoint berikutnya .
Misalnya, anak -anak dari deklarasi Media ini hanya akan terlihat jika lebar viewport sama atau lebih besar dari 1024 poin:
< Media greaterThan = "md" > ... </ Media >Aturan CSS yang sesuai:
@media not all and ( min-width : 1024 px ) {
. fresnel-greaterThan-md {
display : none !important ;
}
}Gunakan ini untuk menyatakan bahwa anak -anak hanya boleh terlihat sementara lebar viewport sama dengan start offset dari breakpoint yang ditentukan atau lebih besar.
Misalnya, anak -anak dari deklarasi Media ini hanya akan terlihat jika lebar viewport adalah 768 poin atau lebih:
< Media greaterThanOrEqual = "md" > ... </ Media >Aturan CSS yang sesuai:
@media not all and ( min-width : 768 px ) {
. fresnel-greaterThanOrEqual-md {
display : none !important ;
}
}Gunakan ini untuk menyatakan bahwa anak -anak hanya boleh terlihat sementara lebar viewport sama dengan start offset breakpoint yang ditentukan pertama tetapi kurang dari start offset breakpoint yang ditentukan kedua.
Misalnya, anak -anak dari deklarasi Media ini hanya akan terlihat jika lebar viewport adalah antara 768 dan 1192 (1192 tidak termasuk) poin:
< Media between = { [ "md" , "xl" ] } > ... </ Media >Aturan CSS yang sesuai:
@media not all and ( min-width : 768 px ) and ( max-width : 1191 px ) {
. fresnel-between-md-xl {
display : none !important ;
}
}Pro:
Kontra:
<Media> yang mereka temukan. Poin terakhir itu menghadirkan masalah yang menarik. Bagaimana kita mewakili komponen yang ditata secara berbeda di breakpoint yang berbeda? (Mari kita bayangkan contoh 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 } />
}Kami masih mencari tahu pola untuk ini, jadi beri tahu kami jika Anda memiliki saran.
Proyek ini menggunakan pelepasan otomatis untuk secara otomatis merilis pada setiap PR. Setiap PR harus memiliki label yang cocok dengan salah satu dari yang berikut
Mayor, Minor, dan Patch akan menyebabkan rilis baru dihasilkan. Gunakan jurusan untuk memecahkan perubahan, minor untuk fitur non-breaking baru, dan tambalan untuk perbaikan bug. Trivial tidak akan menyebabkan rilis dan harus digunakan saat memperbarui dokumentasi atau kode non-proyek.
Jika Anda tidak ingin merilis pada PR tertentu tetapi perubahannya tidak sepele maka gunakan Skip Release tag di samping tag versi yang sesuai.