สมการ Fresnel อธิบายการสะท้อนแสงเมื่อเกิดเหตุการณ์บนอินเทอร์เฟซระหว่างสื่อออพติคอลที่แตกต่างกัน
- 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 >
)
} เมานต์ <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 สามารถทำมากขึ้น สำหรับการดำน้ำลึกอย่างละเอียดลงไปในคุณสมบัติของมันลองดูแอพ Sink Kitchen Sink
หากคุณใช้ Gatsby คุณสามารถลอง Gatsby-Plugin-Fresnel เพื่อการกำหนดค่าที่ง่าย
โซลูชันอื่น ๆ ที่มีอยู่ใช้วิธีการแสดงผลแบบมีเงื่อนไขเช่น react-responsive หรือ react-media ดังนั้นวิธีนี้จะแตกต่างกันที่ไหน?
การแสดงผลด้านเซิร์ฟเวอร์!
แต่ก่อนอื่นการเรนเดอร์แบบมีเงื่อนไขคืออะไร?
ในระบบนิเวศปฏิกิริยาวิธีการทั่วไปในการเขียนองค์ประกอบที่ตอบสนองการประกาศคือการใช้ matchMedia API ของเบราว์เซอร์:
< Responsive >
{ ( { sm } ) => {
if ( sm ) {
return < MobileApp />
} else {
return < DesktopApp />
}
} }
</ Responsive >บนไคลเอนต์เมื่อเบรกพอยต์ที่กำหนดถูกจับคู่ React โดยมีเงื่อนไขแสดงต้นไม้
อย่างไรก็ตามวิธีการนี้มีข้อ จำกัด บางประการสำหรับสิ่งที่เราต้องการบรรลุด้วยการตั้งค่าการเรนเดอร์ฝั่งเซิร์ฟเวอร์ของเรา:
เป็นไปไม่ได้ที่จะรู้จุดพักปัจจุบันของผู้ใช้ในระหว่างขั้นตอนการแสดงผลเซิร์ฟเวอร์เนื่องจากต้องใช้เบราว์เซอร์
การตั้งค่าขนาดเบรกพอยต์ขึ้นอยู่กับการดมกลิ่นของผู้ใช้กับตัวแทนมีแนวโน้มที่จะเกิดข้อผิดพลาดเนื่องจากการไม่สามารถจับคู่ความสามารถของอุปกรณ์กับขนาดได้อย่างแม่นยำ อุปกรณ์มือถือหนึ่งเครื่องอาจมีความหนาแน่นพิกเซลมากกว่าอุปกรณ์อุปกรณ์พกพาอาจพอดีกับจุดพักหลายจุดเมื่อนำการวางแนวอุปกรณ์เข้ามาพิจารณาและบนไคลเอนต์เดสก์ท็อปไม่มีทางรู้เลย devs ที่ดีที่สุดสามารถทำได้คือคาดเดาจุดพักในปัจจุบันและเติม <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 >
</ > |
ดูแอพการเรนเดอร์ฝั่งเซิร์ฟเวอร์สำหรับตัวอย่างการทำงาน
สิ่งแรกก่อน คุณจะต้องกำหนดจุดพักและการโต้ตอบที่จำเป็นสำหรับการออกแบบของคุณเพื่อสร้างชุดของส่วนประกอบสื่อที่คุณสามารถใช้ตลอดแอปพลิเคชันของคุณ
ตัวอย่างเช่นพิจารณาแอปพลิเคชันที่มีจุดพักต่อไปนี้:
smmdlgxlและการโต้ตอบต่อไปนี้:
hovernotHoverจากนั้นคุณจะสร้างชุดของส่วนประกอบสื่อเช่น So:
// 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 แต่ละครั้งตามที่กำหนดไว้ในส่วน 'การตั้งค่า' ด้านบน
หากคุณต้องการหลีกเลี่ยง 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 การเรนเดอร์สามารถถูก จำกัด ให้กับจุดพัก/การโต้ตอบเฉพาะโดยการระบุรายการของการสืบค้นสื่อเพื่อให้ตรงกับ โดยค่าเริ่มต้น ทั้งหมด จะแสดงผล
โดยค่าเริ่มต้นเมื่อแสดงผลฝั่งไคลเอ็นต์ 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 ;
}
}ใช้สิ่งนี้เพื่อประกาศว่าเด็กควรมองเห็นได้ในขณะที่ความกว้างของวิวพอร์ตเท่ากับการชดเชยเริ่มต้นของจุดพักที่ระบุครั้งแรก แต่น้อยกว่าการชดเชยเริ่มต้นของจุดพักที่สองที่ระบุ
ตัวอย่างเช่นเด็ก ๆ ของการประกาศ 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 ทุกครั้งควรมีฉลากที่ตรงกับสิ่งใดสิ่งหนึ่งต่อไปนี้
Major, Minor และ Patch จะทำให้เกิดการเปิดตัวใหม่ ใช้ Major สำหรับการเปลี่ยนแปลงการเปลี่ยนแปลงเล็กน้อยสำหรับคุณสมบัติที่ไม่ใช่การทำลายใหม่และแพตช์สำหรับการแก้ไขข้อผิดพลาด TRIVIAL จะไม่ทำให้เกิดการเปิดตัวและควรใช้เมื่ออัปเดตเอกสารหรือรหัสที่ไม่ใช่โครงการ
หากคุณไม่ต้องการวางจำหน่ายใน PR เฉพาะ แต่การเปลี่ยนแปลงไม่ได้เป็นเรื่องเล็กน้อยให้ใช้แท็ก Skip Release ตามแท็กเวอร์ชันที่เหมาะสม