サーバーサイドレンダリング

(2016/04/26〜2016/06/11)
  • ReactのVirtualDOMから、DOMではなくHTMLを書き出す

  • つまりクライアントではなくサーバーでHTMLをレンダリングする
  • ふつうの静的ページとして配信する
  • 良い所

  • react.jsを読み込ませるよりも帯域を使わない
  • ケータイの回線だと最初に500kbダウンロードするのはつらい
  • JSXからStringに吐き出す方法

  • react-dom/serverにJSXをStringにする関数がある
  • これが
  • render-jsx.js
  • import {renderToStaticMarkup, renderToString} from "react-dom/server";
  • import React, {createElement} from "react";
  • const elm = <div className="test">かずすけ</div>
  • console.log(renderToString(elm));
  • console.log(renderToStaticMarkup(elm));
  • こうなる
  • output.html
  • <div class="test" data-reactroot="" data-reactid="1" data-react-checksum="408212768">かずすけ</div>
  • <div class="test">かずすけ</div>
  • renderToStringはreact用のdata-reactid等が付いたHTML
  • renderToStaticMarkupはプレーンなHTMLが出る
  • コンポーネントを書き出す場合
  • createElement関数をかますとpropsを渡せる
  • render-component.js
  • const MyComponent = ({message, className}) => {
  • return (
  • <div className={className}>{message}</div>
  • );
  • }
  • console.log(renderToStaticMarkup(createElement(MyComponent, {message: "ざんまい", className: "肉"})));
  • output2.html
  • <div class="肉">ざんまい</div>
  • koaならreact-viewを使うとよい

  • semiraraでも使ってる
  • Browserifyでjs以外をtransformして埋め込んでるような部分は自分で書かなければならない
  • なのでStylify使わず普通にlinkタグでcss埋め込んだほうがいい
  • Expressの場合

  • express-react-viewsを使うと簡単
  • 2行で使えるようになる
  • アプリ側の実装(Reduxの場合)

  • クライアント側の実装が綺麗なFluxアーキテクチャになっていると楽
  • 円環の理のstateからviewを表示する一部分だけを切り出せるようになっていればいい
  • ダメな例
  • viewが他のviewを参照している
  • viewの中からajaxして表示を作ってる
  • storeをAppの外側から埋め込める必要がある
  • client/app.js
  • <App store={store} />
  • 埋め込まれたstoreの持つstateだけで表示が全て構築されるように
  • そしてstoreはAppから順に下のコンポーネントへ渡っていく
  • もしくは必要な値だけpropsで渡っていく
  • 初期stateだけでcreateStoreしてAppに渡す
  • javascript
  • export default function IndexStaticHTML({state}){
  • const store = createStore((state) => state, initState);
  • return (
  • <html>
  • <head><title>{state.title}</title></head>
  • <body>
  • <App store={store} />
  • </body>
  • </html>
  • )
  • }
  • stateそのまま返すだけのreducerと、initStateだけ
  • middlewareは使わない
  • renderToStaticMarkupすればonClick内のイベントは潰されるので普通のaタグになる

  • クライアントレンダリングの場合はAjax+pushState
  • サーバーレンダリング時は普通のリンクという風にできる
  • javascript
  • <a href="/shokai/hello" onClick={() => e.preventDefault(); action.route({wiki: "shokai", title: "hello"})} />
  • DOMが必要な処理を書かない

  • もし書くならhas-dom等で環境チェックしてからやる
  • MongoのDocumentをそのままstateとして渡さない

  • サーバーで
  • server.js
  • const page = Pages.findOne({title, wiki});
  • ctx.render("index-static", {state: {page}});
  • するとReact側でstateの中にmongo documentがそのまま渡る
  • React内でpropリレーしていくと、末端でなぜかstateが_docキーの中に入ってしまった
  • documentをふつうのobjectに変換してから渡したほうがいい
  • server.js
  • ctx.render("index-static", {state: {page: page.toObject()}});
  • ツール

  • has-dom
  • % npm install has-dom
  • DOMがある環境かどうか判定、true/falseを返すだけのライブラリ
  • javascript
  • import hasDom from "has-dom";
  • var user = hasDom() ? window.user : null;
  • get-doc
  • % npm i get-doc
  • documentがあれば返す、無ければnull
  • javascript
  • import doc = "get-doc";
  • doc.getElementById("app");
  • なのでnull checkできるcoffee-scriptとかじゃないと結局undefind methodになって意味無さそう