CSSの読み込みを待つReactコンポーネントを作りたかった
※この記事ではstyled-componentやcss-modulesに関しては一切書いておりません。
僕は普段からReactを使ってIsomorphicなWebアプリケーションを開発してるのですが、個人の趣向でcss-modulesを使っておらずJavaScriptとCSSはそれぞれ独立させて作られています。
この環境下でSPAで遷移する時に各ページ毎にJSやCSSファイルが存在するため、ページ遷移毎にその読み込みを待つ時間が少なくとも発生します。このときCSSファイルがまだ読み込まれずにコンポーネントが描画されると、スタイルがあたっていない状態で視認できる状態になってしまいます。ちなみにページ単位でのCSSの読み込みではreact-helmetを用いていました。
このスタイルがあたっていない状態がちらつく減少を回避するためにはリンクタグで読み込んでいるCSSのロード完了を待つ必要がありますが、残念ながら使用しているライブラリ側ではそのような昨日はサポートされていませんでした。
といった経緯で作ったのがこちら
これが何をするかと言うと、非同期で読み込みたいCSSとソレに依存するコンポーネントをCSSのロードを待って表示させるというものになります。
もちろんIsomorphicな環境下でも使用できるので、その際は収集したCSSリソース群をHTMLタグに吐き出しServer Side Renderingで描画することも可能になりました。
import * as React from 'react'; import { CSSCollector } from 'react-css-context'; export class SomeComponent extends React.Component { return ( <CSSCollector hrefs={["${絶対URLを指定}"]}> <div>display after css loaded.</div> </CSSCollector> ); }
2. クライアントサイドでの実装
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { CSSProvider, getCSSMap } from 'react-css-context'; // すでにhead内にSSRで描画されたlinkタグがあればそこから初期値を生成 const cssMap = getCSSMap(); ReactDOM.render( <App> <CSSProvider cssMap={cssMap}> <SomeComponent /> </CSSProvider> </App>, document.getElementById('main') );
3. サーバーサイドでの実装
import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { CSSProvider, toTagString, toTagComponent } from 'react-css-context'; // Mapを作成 const cssMap = new Map(); const renderOutput = ReactDOMServer.renderToString( <App> <CSSProvider cssMap={cssMap}> <SomeComponent /> </CSSProvider> </App> ); // 文字列でHTMLを作るとき const htmlOutput = ` <html> <head> ${toTagString(cssMap)} </head> ... </html> `; // JSX記法でHTMLをつくるとき const htmlOutput = renderToString( <html> <head> {toTagComponent(cssMap)} </head> ... </html> );
他、ローディング中の代替表示や読み込み失敗時の表示も指定可能です。 あとReactの非同期レンダリングもサポートしています。
Official Context API
react-css-contextはReact v16.3から出たContext APIで実装されています。
今更なおさらいになりますが、Context APIが何かと言うとReactアプリケーション全体で使えるデータを渡すものになります。
通常Reactの世界観ではデータはpropsを経由して子に引き継がれていきます。バケツリレーのように親から子へと情報の受け渡しをしても良いのですが、例えばアプリケーション全体で使い回されるような値を扱う時や階層が深くなっていくとこのpropsだけでの運用だと実装が非常に面倒になってきます。
Contextを用いればこういった値をどこでも共有することができるようになります。
Reduxを用いても実装は可能ですが、今回はReactだけで完結して実装するためにContextを使いました。
すこしコツはいりますがContextで扱うデータは編集可能なので こちらを参考に 実装してみてください。
以上、新しいAPIを使ってみたいミーハーな僕よりお届けしました。