Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Progressive Hydration #react_fukuoka
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Hiroyuki ANAI
June 05, 2019
Programming
1.9k
6
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Progressive Hydration #react_fukuoka
Hiroyuki ANAI
June 05, 2019
More Decks by Hiroyuki ANAI
See All by Hiroyuki ANAI
安心して変更できるWebフロントエンドの作り方
pirosikick
4
2.2k
書き換えて学ぶTemporal #fukts
pirosikick
2
440
fukuoka.ts #3 社内でESLintの共通設定を配りたい2025年春版
pirosikick
3
500
compilerOptions、全部読んだ
pirosikick
1
310
Step Functionsの設計時に知っておいたほうがいいかもしれないこと
pirosikick
0
550
Go言語による並行処理「4.4 orチャネル」の図
pirosikick
0
500
サイボウズWebフロントエンド脱レガシーの今までとこれから
pirosikick
6
17k
@cybozu/eslint-configから学ぶ、全社共通ESLint configの運用
pirosikick
4
1.9k
Web Share Target API #w3fukuoka
pirosikick
0
740
Other Decks in Programming
See All in Programming
Foundry Localでエージェント開発
seosoft
0
190
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
790
App Intentsのビルドプロセスを支える技術
kntkymt
0
350
20260828_品質と開発生産性を両立させる、AI時代のE2Eテストの考え方
magicpod
0
200
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
380
アクセシビリティから考える情報設計
high_g_engineer
0
370
自分的「カンファレンスの楽しみ方」
syumai
0
200
Family mrubyの進捗
kishima
1
120
[DroidKaigi 2026] Bring your own phones to Gradle Managed Devices
f2lk
0
120
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
180
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
4
1.1k
Featured
See All Featured
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
700
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
330
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.8k
Designing Powerful Visuals for Engaging Learning
tmiket
1
540
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Accessibility Awareness
sabderemane
1
210
Designing for Timeless Needs
cassininazir
1
470
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.8k
The Language of Interfaces
destraynor
162
27k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
460
Statistics for Hackers
jakevdp
799
230k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Transcript
1SPHSFTTJWF )ZESBUJPO !QJSPTJLJDL ʢ8FEʣ 3FBDUษڧձ!ԬWPM
w !QJSPTJLJDL w αΠϘζגࣜձࣾ ϑϩϯτΤϯυΤΩεύʔτνʔϜ w స৬͠·ͨ͠! ԬͷΤϯδχΞ·ͩҰਓͰ͕͢ ͨͷ͍͠৬Ͱ͢ w
ԬͰ࠾༻ͯ͠·͢ ࣗݾհ
͢͜ͱ w )ZESBUJPOͷ͓͞Β͍ w 1SPHSFTTJWF)ZESBUJPOʹ͍ͭͯ
)ZESBUJPOͷ͓͞Β͍
)ZESBUJPOͱʁ w 4FSWFS4JEF3FOEFSJOHʢҎԼɺ443ʣʹΑͬͯ ඳը͞ΕͨίϯςϯπΛɺ w $MJFOU4JEF3FOEFSJOHʢҎԼɺ$43ʣͰ͍ճ͢͜ͱ
$43POMZ w ίϯϙʔωϯτͷπϦʔ͔Β%0.Λੜɺඳը w Πϕϯτϋϯυϥʔͷઃఆ // client.js import React from
'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( <App />, document.getElementById('root') );
443 )ZESBUJPO w αʔόʔαΠυ w ίϯϙʔωϯτπϦʔΛ)5.-ͷจࣈྻʹม͠ɺϨεϙϯε͢Δ • ReactDOMServer.{renderToString|renderToNodeStream} // server.js
const ReactDOMServer = require('react-dom/server'); …省略 app.get('/', (req, res) => { const html = ReactDOMServer.renderToNodeStream( <Html><App /></Html> ); html.pipe(res); });
443 )ZESBUJPO w ΫϥΠΞϯταΠυ w 443͕ฦ͢)5.-Ͱ%0.͕ߏஙɾඳըࡁΈ w ΠϕϯτϋϯυϥʔͷઃఆͷΈΛߦ͏ w 3FBDU%0.SFOEFSͰͳ͘ɺ3FBDU%0.IZESBUFΛ͏
// client.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate( <App />, document.getElementById('root') );
$41POMZ )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 +4͕࣮ߦ͞ΕΔ·Ͱը໘͕ਅͬന '$1'JSTU$POUFOUGVM1BJOUɺ ϖʔδભҠ͔Β࠷ॳͷίϯςϯπඳը·Ͱͷ࣌ؒ
443 )ZESBUJPO )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 '$1 '$1͕͘ͳΔ 443Ͱ ඳը͞ΕΔ
͜͜·Ͱͷ·ͱΊ w )ZESBUJPO443ͷ݁ՌΛ$43Ͱ͍ճ͢͜ͱΛݴ͏ w 443ʴ)ZESBUJPOʹΑͬͯ'$1͕͘ͳΔ w αʔόʔαΠυͰ 3FBDU%0.4FSWFSSFOEFS5P4USJOH͔ 3FBDU%0.4FSWFSSFOEFS5P/PEF4USFBNΛ͏ w
ΫϥΠΞϯταΠυͰ 3FBDU%0.SFOEFSͰͳ͘3FBDU%0.IZESBUFΛ͏
1SPHSFTTJWF )ZESBUJPO ʹ͍ͭͯ
)ZESBUJPOͷ w '$1͘ͳΔ͕55*͘ͳΒͳ͍ w 55*5JNF5P*OUFSBDUJWF w ϖʔδ͕ΠϯλϥΫςΟϒʢૢ࡞Մೳʣʹ ͳΔ·Ͱͷ࣌ؒ w ඳը͞Εͯૢ࡞ՄೳʹͳΔͷɺ+4͕࣮ߦ͞Εͨޙ
$41POMZ )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 55*
443 )ZESBUJPO )551(&5 +4ͷμϯϩʔυ +4ͷ࣮ߦ '$1 55*
1SPHSFTTJWF)ZESBUJPO w )ZESBUJPOΛஈ֊తʹߦ͏͜ͱͰ55*Λॖ͢Δ w (PPHMF*0Ͱհ͞ΕͨςΫχοΫ w 3FOEFSJOHPOUIF8FC 1FSGPSNBODF*NQMJDBUJPOTPG"QQMJDBUJPO"SDIJUFDUVSF IUUQTZPVUVCFL"7GV630H
443 )ZESBUJPO )551(&5 +4ͷ%- +4ͷ࣮ߦ '$1 55* Ұؾʹ)ZESBUJPO
1SPHSFTTJWF)ZESBUJPO +4ͷ࣮ߦ 55* ඞཁͳ͚ͩ)ZESBUJPO )551(&5 +4ͷ%- '$1 +4ͷ࣮ߦ Γͷ)ZESBUJPO ҰؾʹΔΑΓ
͘ͳΔ ඞཁʹͳͬͨΒ )ZESBUJPO͢Δ ɾɾɾ
σϞ w (PPHMF͕σϞΛެ։͍ͯ͠Δ w IUUQTHJUIVCDPN(PPHMF$ISPNF-BCTQSPHSFTTJWF SFOEFSJOHGSBNFXPSLTTBNQMFT w ϑΝʔετϏϡʔͷޙʹ݅ͷϦετ͕͋Δϖʔδ w Ϧετ෦࠷ॳͷ)ZESBUJPOͷର֎
w Ϧετ෦ը໘ʹೖͬͨλΠϛϯάͰ )ZESBUJPO͞ΕΔ
ϑΝʔετϏϡʔ ࠷ॳʹ)ZESBUJPO͞ΕΔ Ϧετ෦ ը໘ʹೖͬͨΒ)ZESBUJPO͞ΕΔ
"VEJUTͰൺֱ 1)ͳ͠ 1)͋Γ 55*͕ඵߴʹ
αϯϓϧͷιʔείʔυΛ ಡΉ w BQQKT w ϧʔτίϯϙʔωϯτ͕ఆٛ͞Ε͍ͯΔ w TUSFBNKTʢ˞ࠓճಡ·ͳ͍ʣ w Ϧετ෦ͷ4USFBNίϯϙʔωϯτ͕ఆٛ͞Ε͍ͯΔ
w IZESBUPSKT w 1SPHSFTTJWF)ZESBUJPOΛ࣮ߦ͢Δίϯϙʔωϯτ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } BQQKT ϧʔτίϯϙʔωϯτʢ"QQʣ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } $43Ͱ%ZOBNJD*NQPSUͰ 4USFBNίϯϙʔωϯτΛಡΈࠐΉ 4USFBNίϯϙʔωϯτ #VOEMF͕͔Εɺ ಈతʹϒϥβʹϩʔυ͞ΕΔ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } 443࣌੩తʹ 4USFBNίϯϙʔωϯτΛಡΈࠐΉ
// app.js … let load = () => import('./stream'); let
Hydrator = ClientHydrator; if (typeof window === 'undefined') { Hydrator = ServerHydrator; load = () => require('./stream'); } export default function App() { return ( <div id="app"> <Header /> <Intro /> <Hydrator load={load} /> </div> ); } )ZESBUPSίϯϙʔωϯτ͕ 1SPHSFTTJWF)ZESBUJPOΛ࣮ߦ͢Δ QSPQTMPBE͕ฦ͢ίϯϙʔωϯτ Ԇͯ͠)ZESBUJPO͞ΕΔ
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } IZESBUPSKT )ZESBUPS$MJFOU)ZESBUPS
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w TIPVME$PNQPOFOU6QEBUFͰ ৗʹGBMTFΛฦ͍ͯ͠ΔͷͰ Ξοϓσʔτ͞Εͳ͍
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w ۭͷཁૉΛग़ྗɺ ཁૉͷSFGΛऔಘ w EBOHFSPVTMZ4FU*OOFS)5.-Ͱ ۭจࣈΛઃఆ͢Δͱɺ )ZESBUJPOΛᷖճ͢Δ
// hydrator.js … export class Hydrator extends React.Component { shouldComponentUpdate()
{ return false; } componentDidMount() { new IntersectionObserver(async ([entry], obs) => { if (!entry.isIntersecting) return; obs.unobserve(this.root); const { load, ...props } = this.props; const Child = interopDefault(await load()); ReactDOM.hydrate(<Child {...props} />, this.root); }).observe(this.root); } render() { return ( <section ref={c => this.root = c} dangerouslySetInnerHTML={{ __html: '' }} suppressHydrationWarning /> ); } } w *OUFSTFDUJPO0CTFSWFSͰ ཁૉͷεΫϩʔϧҐஔΛࢹ w ཁૉ͕ը໘ʹೖͬͨΒɺ )ZESBUJPOΛ࣮ߦ
w αʔόʔαΠυී௨ʹ443ΛΔ w )ZESBUPSίϯϙʔωϯτͷԼ ࠷ॳͷ)ZESBUJPOΛᷖճ͢Δ w EBOHFSPVTMZ4FU*OOFS)5.-ʹۭจࣈΛ͢ w )ZESBUPSίϯϙʔωϯτͷԼ͕ ը໘ʹೖͬͨΒͦͷ෦ͷ)ZESBUJPOΛ࣮ߦ
w *OUFSTFDUJPO0CTFSWFSͰࢹ͠ɺ 3FBDU%0.IZESBUFΛ࣮ߦ
·ͱΊ
w 1SPHSFTTJWF)ZESBUJPO ஈ֊తʹ)ZESBUJPO͢Δ͜ͱͰ55*Λॖ͢Δ w ݱঢ়)BDLZͳ࣮͕ͩɺެࣜʹରԠ༧ఆ͕͋Δ w "OHVMBSɺ7VFͰͰ͖ΔʢΒ͍͠ʣ
1SPHSFTTJWF)ZESBUJPO ಋೖ͖͔͢ʁ w ಋೖʹϦεΫ͕΄΅ͳ͍ w )ZESBUPSίϯϙʔωϯτΛऔΓআ͚ͩ͘ͰݩʹͤΔ w ݱঢ়ϋοΫͬΆ͍࣮ɻόʔδϣϯΞοϓ࣌ʹҙ͕ඞཁ͔ w 55*͕ಛผ͍ॴͰಋೖ͢Δͷ͕Αͦ͞͏
w 55*͕͍ॴͰޮՌ͕ബ͍ͷͰɺ શϖʔδͰಋೖ͖͔͢ඍົ w "VEJUTͰܭଌ͠·͠ΐ͏
w (PPHMF*0ͷ8FCΛ·ͱΊΔձ IUUQTNFOUBJDPKTDPOOQBTTDPNFWFOU w ʢਫʣ w ·ۭ͍ͩͯ·͢ͷͰɺͥͻʙ
͋Γ͕ͱ͏ ͍͟͝·ͨ͠