React+jQuery+Railsã®SPAããµã¼ããµã¤ãã¬ã³ããªã³ã°ã«ç§»è¡ãã件ï¼ãã®2ï¼ãã©ã¦ã¶ä¾åæé¤ç·¨ï¼
ã¨ããããã§ãååã®ç¶ãã§ã...Ï(ï½¥Ïï½¥ï½)
ï¼ååï¼
parrot.hatenadiary.jp
ï¼ãµã¤ãï¼
ccpts.parrot-studio.com
ï¼ä¿®æ£ããã³ã¼ãï¼
github.com
ååã¯ããµã¼ããµã¤ãã¬ã³ããªã³ã°ï¼SSRï¼ãã®æ¦å¿µçãªè©±ã¨ã
ããããå°ãããè¨è¨ã®æ¦è¦ãããã¦SSRã«ç§»è¡ããããã®æ®µåãã«ã¤ãã¦æ¸ãã¾ãã
- ãã©ã¦ã¶ç³»ãªãã¸ã§ã¯ãã®æé¤ï¼ã¨ããããå®è¡æã¨ã©ã¼ãæ¶ãï¼
- ãä»æ§ä¸ã®æ£ããåä½ãã«ãªãããã«ä¿®æ£
- ã¤ã³ãã©ã®èª¿æ´
ä»åã¯ãã®ä¸çªæåãããã©ã¦ã¶ç³»ãªãã¸ã§ã¯ãã®æé¤ããããã£ã¦ããã¾ã
STEP1ï¼å®è¡æã¨ã©ã¼ã®æ¶å»
ã¨ã«ããã¾ãã¯ãã¨ã©ã¼ãåºãªãããä½ãç»é¢ã表示ãããããç®æãã¾ã
ååãwindowãªãã¸ã§ã¯ããæé¤ããã°ã¨ã©ã¼ã«ãªããªãã®ã§ã¯ã»ã»ã»ã¨ããæ¨æ¸¬ãã¾ãããã
ãããç«è¨¼ããã®ãæåã®ã¹ãããã«ãªãã¾ã
(1) äºã¤ã®é åã®jsã¨ããããã¤ãªãProxy
å
·ä½çãªRailsã®è©±ã§ãããååæ¸ãã話ã«ãªãã¾ããã
Railsãæ±ãjsã¯ãã£ãã2種é¡ããã¾ã
Railsã®assetsã«å±ãããapplication.jsãã¨ã
webpackã§ã¾ã¨ããããSSRã«ã使ããããReactã主ä½ã«ããjsã
ï¼ä»¥ä¸ãã¢ããªåã«ãããã¦ãccpts.jsãã¨è¡¨è¨ï¼ã§ã
åè
ã¯ãapp/assets/javascriptsããå¾è
ã¯ãapp/javascriptãã«åå¨ãã
ããããå¥ãªã«ã¼ãã§ã¾ã¨ãããã¦ä¸ã¤ã«ãªãã¾ã*1
ãapplication.jsãã¯ï¼HTMLã®layoutãçµç±ãã¦ï¼ã¯ã©ã¤ã¢ã³ãã®ã¿ã§èªã¿è¾¼ã¾ãã¾ããã
ãccpts.jsããµã¼ãã¨ã¯ã©ã¤ã¢ã³ãåæ¹ã§å®è¡ããã¾ã
ãã®ãccpts.jsãã«windowãªãã¸ã§ã¯ãçãå«ã¾ããã¨ã¾ãããã¨ã«ãªãã¾ã(´-Ï-)
ãªãã°ãwindowã«è§¦ããã³ã¼ããapplication.jsã«è¿½ãåºãã¦ãã¾ããã»ã»ã»ã¨ãããã¨ã§ã
ã¾ããwindowã¨ãjQueryã¨ãããã©ã¦ã¶ç³»ã®ãªãã¸ã§ã¯ãã«è§¦ãã³ã¼ããã
ãBrowserãã¨ãããªãã¸ã§ã¯ãã«éãè¾¼ãã¦ãã¾ãã¾ã
// app/assets/javascripts/browser.js ã®æç² // webpackãéããªãã®ã§ãTypeScriptã§ã¯ãªã // ã¯ã©ã¹çã«æ¯ãèãã°ãã¼ãã«ãªãã¸ã§ã¯ããå®ç¾© Browser = {}; // window.location Browser.thisPage = function () { return window.location.href; } // window.confirm Browser.confirm = function (mes) { return (window.confirm(mes)); } // jQueryã«ä¾åããå¦çã®ä¾ Browser.addSwipeHandler = function (div, callbackLeft, callbackRight) { if (!div) { return; } $(div).swipe({ swipeLeft: (function (e) { e.preventDefault(); callbackLeft(); }), swipeRight: (function (e) { e.preventDefault(); callbackRight(); }) }); }
å
ã
ReactåããéãjQueryãªã©ã®ã©ã¤ãã©ãªã¯ãapplication.jsãã«è¿½ããããã¦ããã®ã§ã
ããã§windowã«è§¦ãã³ã¼ãã¨ã©ã¤ãã©ãªãå
¨ã¦ãapplication.jsãã«åé¢ããããã¨ã«ãªãã¾ã *2
ãããããã®ã¾ã¾ã§ã¯Reactå´ã§çæããDOMã¨ããããã®å¦çãé£æºã§ãã¾ãã
ããã§ããã¯ã©ã¤ã¢ã³ããµã¤ãã§ã¯Browserã«å¦çãå§è²ãã
ãµã¼ããµã¤ãã§ã¯é©å½ãªå¤ãè¿ããªãã¸ã§ã¯ãï¼âã¯ã©ã¹ï¼ãã¨ãã¦ã
ãBrowserProxyããå®ç¾©ãã¾ã
// BrowserProxyã®æç² // ä»ã®ã³ã¼ããã追ãåºãããdeclare // ããã«ããdeclareããªãã®ã§ãä»ã®ã³ã¼ãã«å¤é¨ä¾åããªããã¨ãä¿è¨¼ã§ãã declare var window declare var Browser export default class BrowserProxy { // windowãªãã¸ã§ã¯ãï¼ã¨Browserãªãã¸ã§ã¯ãï¼ã®æç¡ãå¤å® public static isWindowDefined: boolean = (() => { if (typeof window === "undefined") { return false } if (typeof Browser === "undefined") { return false } return true })() // windowããªããã°ç¹å®ã®å¤ãè¿ãä¾ public static thisPage(): string { if (BrowserProxy.isWindowDefined) { return Browser.thisPage() } return "/" } // windowãããã¨ãã ãhandlerãã»ããããä¾ public static addSwipeHandler(div, callbackLeft, callbackRight): void { if (BrowserProxy.isWindowDefined) { Browser.addSwipeHandler(div, callbackLeft, callbackRight) } } }
ãã®ããã«ãwindowãBrowserãåå¨ããï¼âã¯ã©ã¤ã¢ã³ããµã¤ãï¼ã§ã¯ã
ã¾ãã¾Browserã«å¦çãå§è²ãã¦ãã¾ããã
Browserãåå¨ããªãï¼âãµã¼ããµã¤ãï¼ç°å¢ã§ã¯ä½ãããªãããé©å½ãªå¤ãè¿ãã¦ãã¾ã
ãã§ã«TypeScriptåãã¦ããã®ã§ãå¤é¨åç
§ãã¦ããç®æã¯
ãdeclare $ãã¨ããdeclare locationãã¨ããdeclare宣è¨ããã¦ããã®ã§ã
ãããBrowserã¨BrowserProxyã«ã©ãã©ãåãåºãã¦ããã¾ã*3
å¼ã³åºãå´ã¯ãããªæãã§ã
// é¢é£ããç®æã ãæç² export default class DatabaseTableArea extends ResultView<ResultViewProps> { private arcanaTable: HTMLTableElement | null = null // componentããã¦ã³ãããæã«ãã³ãã©ãã»ããï¼ãã ããã¯ã©ã¤ã¢ã³ããªãã°ï¼ public componentDidMount(): void { Browser.addSwipeHandler( this.arcanaTable, this.handleLeftSwipe.bind(this), this.handleRightSwipe.bind(this) ) } }
ãã®æ®µéã§ã¯ãSSRã¨ç¡é¢ä¿ã«ããã ã³ã¼ããæ´çãã¦ããã ãã§ãã®ã§ã
å¦çãåãåºãããSSRãªãã§åä½ãå¤ãããªããã¨ã確èªã»ã»ã»ã¨ããæãã§é²ãã¾ã
(2) æ«å®çCookie対å¿
ããã¾ã§ã®ä½æ¥ãçµãã£ãã¨ããã§ã試ãã«SSRã«åãæ¿ããã¨ããã
ãwindow not foundãã§ã¯ãªãã¨ã©ã¼ã«å¤ããã¾ãã
ããã¯ããã§ä¸æ©åé²ã§ãããã¨ã©ã¼ã®å
容ã調ã¹ãã¨ããã
Cookieãèªãã§åæåããã¨ããã§è½ã¡ã¦ã¾ãã(´-Ï-)
ãã®æç¹ã§ã¯ãµã¼ããµã¤ãJSå®è¡ç³»ã«ãããCookieã®æ±ããããããªãã£ãã®ã§ããã»ã»ã»
- Cookieã«è§¦ãããã¨èªä½ã¯ã¨ã©ã¼ã®åå ã§ã¯ãªãï¼ãã åå¨ããªãã£ãã¨ããæåï¼
- Cookieã¸ã®æ¸ãè¾¼ã¿ã§ã¯è½ã¡ã¦ããªãããã«è¦ããï¼ãã®æç¹ã§ã¯æåä¸æï¼
ã»ã»ã»ã¨ãããã¨ã§ããµã¼ããµã¤ãã§Cookieã®ãªãã¸ã§ã¯ããparseãã
Reactã®åæå¤ã¨ãã¦æ§é ã丸ãã¨æ¸¡ãã»ã»ã»ã¨ããå¼·å¼ãªæã«Î£(ã»Ïã»ã)ã
ããã¯ããã¾ã§ä¸æçãªå¦ç½®ã§ã次ã®STEP2ã§é©åã«ç½®ãæããããã®ã§ããã
ã¨ã«ãããã®æ®µéã§ã¯ãã¨ã©ã¼ãæ¶ããï¼ããã¦BrowserProxyæ¹å¼ã®å¦¥å½æ§ãæ¤è¨¼ããï¼ã®ã
æåªå
ãªã®ã§ãç¡çç¢çãªæ段ãã¨ãã¾ãã
ãã®ä¿®æ£ã«ãããã¤ãã«SSRã§HTMLãåãåºããã
ããã£ã½ãç»é¢ã表示ããã¾ããã»ã»ã»ãã
ããã¯æ°ããªæ¦ãã®å§ã¾ãã§ãããªãã£ãã®ã§ãã»ã»ã»
ä»åã®ã¾ã¨ãã¨æ¬¡åäºå
ä»åã¯å®éã®ä¾ã¨ãã¦ãRailsã®æèã§ã®ä½æ¥ã«ã¤ãã¦æ¸ãã¦ã¿ã¾ãããã
ä¸è¬çãªãã¤ã³ãã¯ä»¥ä¸ã§ã...Ï(ï½¥Ïï½¥ï½)
- jsã2ã¤ã«åé¢ãã
- ãã¯ã©ã¤ã¢ã³ãã§ã ãå®è¡ããããã®ãã¨ããµã¼ãã»ã¯ã©ã¤ã¢ã³ãåæ¹ã§å®è¡ããããã®ã
- ã¯ã©ã¤ã¢ã³ãã§ã ãå®è¡ãããjsã«ããã©ã¦ã¶ç³»ãªãã¸ã§ã¯ããåç §ããã³ã¼ããéãã
- ãã©ã¦ã¶ç³»ãªãã¸ã§ã¯ãã®å¦çã代è¡ããProxy層ãç¨æãã
ããã§ã表示ãã¾ã§é²ããã¨ãã§ãã
SSRèªä½ããããããªæ°ãããã¬ãã«ã«ã¯ãªã£ãã®ã§ããã
é²ãã¦ããã¨ããã¯ã©ã¤ã¢ã³ããã¨ããµã¼ãããå¥ãªå½¢ã§æèããããããã¨ã«ãªãã¾ã
ã¨ãããã¨ã§ã次åãSTEP2ï¼ä»æ§å¤æ´ç·¨ãã¸ç¶ãã¾ã(`ï½¥Ï・´)
*1: åè ã¯sprocketsãå¾è ã¯webpacker
*2: ã©ããã§æ¸ããã¨æã£ãããã¡ããã¨æ¸ãã¦ãªãã£ãæ°ããããã©ãã¨ãããããã®ããã https://parrot.hatenadiary.jp/entry/2016/02/28/113310
*3: declareã«ã¤ãã¦ã¯ä»¥åã®è¨äºåç § https://parrot.hatenadiary.jp/entry/2018/08/29/171654