LoginSignup
34

More than 5 years have passed since last update.

RailsでReduxコンテナをサーバサイドレンダリング (use Hypernova by airbnb)

Posted at

はじめに

前回の記事 RailsでReactをサーバサイドレンダリング(use Hypernova by airbnb) - Qiitaでは、ReactコンポーネントをRailsアプリで簡単にサーバサイドレンダリングに対応する方法を書きました。

最近ではReact単体ではなく、コンポーネント群の状態(State)を一元管理してデータの流れを一方向に整える開発技法Fluxを取り入れたRedux(react-redux)を用いたアプリケーションも増えてきました。

前回ご紹介したHypernovaでは、Reactコンポーネントのサーバサイドレンダリングには対応していたのですが、Reduxコンテナ1はうまくレンダリング出来ませんでした。

そこで、この記事ではReduxコンテナのサーバサイドレンダリングに対応したHypernovaのReact/Reduxバインディングであるnoriaki/hypernova-react-reduxを紹介すると共に、シンプルなカウンターアプリをサーバサイドレンダリングで表示するまでのチュートリアルを解説します。

前回記事のチュートリアルを完了している前提で、変更点を追いかけるかたちで書いていきますので、まだの方は先に試してみてください。

Hypernova/ReactRedux

airbnb製のHypernova Reactバインディング(airbnb/hypernova-react)を改良して、Reduxコンテナのサーバサイドレンダリングが可能になるHypernova用バインディングとして作成しました。

noriaki/hypernova-react-redux: React/Redux (react-redux) bindings for Hypernova.

Hypernova/serverHypernova/rubyのコードを変更することなく、Hypernova/reactを置き換えることが出来るように設計しています。

Hypernova/ReactReduxを使ったサーバサイドレンダリングのチュートリアル

Reduxを利用したカウント{アップ|ダウン}ができるカウンターアプリを実装します。
また、ES2015形式やReact JSX形式の記載ができるようbabelによるトランスパイラも使いますが、トランスパイルはbrowserify-railsが実行してくれますのでご安心ください。

環境・バージョン

前回記事と同様ですが再掲しておきます。

environments
# mac
MacBook Air (13-inch, Mid 2012) - OS X El Capitan (10.11.5)

# ruby/rails
% ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
% gem -v
2.5.1
% rails -v
Rails 4.2.6

# node/npm
% node -v
v5.5.0
% npm -v
3.7.1

1. Railsアプリの作成 〜 3. browserify-railsの導入

前回記事のチュートリアルと同様です。

4. JavaScriptの環境設定 (package.json)

ES2015やReact JSXの変換のためにいくつかパッケージを追加しています。

前回記事のチュートリアルの続きの人

以下のコマンドを実行してパッケージを追加します。

% npm i -D babelify babel-preset-es2015 babel-preset-react babel-cli
% npm i -S hypernova-react-redux react-redux redux
package.jsonの差分
   "dependencies": {
     "hypernova": "^1.0.0",
-    "hypernova-react": "^1.0.0",
+    "hypernova-react-redux": "^1.0.0",
     "react": "^15.1.0",
-    "react-dom": "^15.1.0"
+    "react-dom": "^15.1.0",
+    "react-redux": "^4.4.5",
+    "redux": "^3.5.2"
   },
   "devDependencies": {
+    "babel-cli": "^6.10.1",
+    "babel-preset-es2015": "^6.9.0",
+    "babel-preset-react": "^6.11.1",
+    "babelify": "^7.3.0",
     "browserify": "^13.0.1",
     "browserify-incremental": "^3.1.1"
   }

今回記事のチュートリアルからの人

完成したpackage.jsonを載せておきますので、#{Rails.root}にファイルを作りnpm installを実行します。

完成したpackage.json
{
  "name": "hypernova-getting-started-with-rails",
  "version": "1.0.0",
  "description": "A sample Rails application that uses Hypernova to server render.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\""
  },
  "author": "Noriaki Uchiyama",
  "license": "MIT",
  "dependencies": {
    "hypernova": "^1.0.0",
    "hypernova-react-redux": "^1.0.0",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "react-redux": "^4.4.5",
    "redux": "^3.5.2"
  },
  "devDependencies": {
    "babel-cli": "^6.10.1",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.11.1",
    "babelify": "^7.3.0",
    "browserify": "^13.0.1",
    "browserify-incremental": "^3.1.1"
  }
}

5. Hypernova/serverの設定 〜 6. Hypernova/rubyの設定

前回記事のチュートリアルと同様です。

7. Hypernova/react-reduxを使用したReduxコンテナ

ここが前回と大きく違います。前回、propsから文字列を表示するコンポーネントを実装していたMyComponent.jsを、以下のようにごっそり置き換えます。

app/assets/javascripts/MyComponent.js
import React, { PropTypes } from 'react';
import { renderReactRedux } from 'hypernova-react-redux';

import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';

// Component
function Counter(props) {
  const { count, onIncrement, onDecrement } = props;
  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={onIncrement}>++</button>
      <button onClick={onDecrement}>--</button>
    </div>
  );
}
Counter.propTypes = {
  count:       PropTypes.number.isRequired,
  onIncrement: PropTypes.func.isRequired,
  onDecrement: PropTypes.func.isRequired
};

// Actions
const INCREMENT_COUNTER = {
  type: 'INCREMENT_COUNTER'
};
const DECREMENT_COUNTER = {
  type: 'DECREMENT_COUNTER'
};

// Reducer
function counterReducer(state = { count: 0 }, action) {
  switch(action.type) {
  case 'INCREMENT_COUNTER':
    return { count: state.count + 1 };
  case 'DECREMENT_COUNTER':
    return { count: state.count - 1 };
  default:
    return state;
  }
}

// configureStore
function configureStore(preloadState = {}) {
  const initialState = counterReducer(undefined, {});
  return createStore(
    counterReducer,
    Object.assign({}, initialState, preloadState)
  );
}

// connect (react-redux)
function mapStateToProps(state) {
  return { count: state.count };
}

function mapDispatchToProps(dispatch) {
  return {
    onIncrement() { return dispatch(INCREMENT_COUNTER); },
    onDecrement() { return dispatch(DECREMENT_COUNTER); }
  };
}

let ConnectedMyComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);


module.exports = renderReactRedux(
  'MyComponent.js',
  ConnectedMyComponent,
  configureStore
);

前回と同様、重要なのはrenderReactRedux関数で、第一引数にコンポーネント名、第二引数にコンポーネント実体を受け取る点はrenderReact関数と同じですが、第三引数にconfigureStore関数2を受け取っています。

なお、通常このファイルに記載している内容は、ComponentActionReducerなどは別ファイルに分けるのですが、今回はすべて一つのファイルに記載しました。

別ファイルに分ける場合、MyComponent.jsは以下のようになります。(各コンポーネントのimport元は適切に書き換えてください)

app/assets/javascripts/MyComponent.js
import renderReactRedux from 'hypernova-react-redux';
import ConnectedMyComponent from './PATH/TO/Containers/ConnectedComponent';
import configureStore from './PATH/TO/Store/configureStore';

module.exports = renderReactRedux(
  'MyComponent.js',
  ConnectedMyComponent,
  configureStore
);

8. RailsアプリからHypernova呼び出し

前回記事のチュートリアルからいくつか変更を加えています。
app/controllers/welcome_controller.rbhypernova_render_supportメソッドや、app/views/layouts/application.html.erbは前回の記事を参考にしてください。

8.1. Viewからコンポーネントを呼び出す

app/views/welcome/index.html.erbを以下のいずれかに書き換えます。

State情報をRailsから渡さない(Reduxカウンターアプリ内の初期Stateを利用する)場合
app/views/welcome/index.html.erb
<%= render_react_component('MyComponent.js') %>
RailsからState情報を渡す場合

この場合カウンターの初期値が10になります。

app/views/welcome/index.html.erb
<%= render_react_component('MyComponent.js', count: 10) %>

8.2. Babelによるトランスパイル設定

(7)のReduxコンテナをES2016やReact JSXの記法で実装したので、browserify-railsがこの記法を変換できるように以下を追記します。

config/application.rb
# browserify-rails with babelify (es2015, react)
config.browserify_rails.commandline_options =
  "-t [ babelify --presets [ es2015 react ] ]"

9. 動作を確認してみる

Railsアプリを起動します。

% bin/rails server

起動したら、ブラウザで http://localhost:3000/welcome/index へアクセスし、カウント: 0回などと表示され、++--のボタンクリックでカウント表示が増減することが確認できるでしょう。

ページのソースを確認してみると、該当部分は以下のようになっていると思います。

<div data-hypernova-key="MyComponentjs"></div>
<script type="application/json" data-hypernova-key="MyComponentjs"><!--{}--></script>

前回も出てきましたが、これはHypernova/serverを起動していないので サーバサイドレンダリングを利用していない アプリとして動作しています。

サーバサイドレンダリング機能を有効にする

Hypernova/serverサービスを起動すると、他のコードは変更しなくてもサーバサイドレンダリングが有効になります。

今回はbabelによるトランスパイルが必要になっているので、サービス起動のためのnpmスクリプトを設定し利用します。具体的にはpackage.jsonに以下を追加します(diffによる差分)。

package.jsonの差分
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\""
+    "test": "echo \"Error: no test specified\"",
+    "hypernova": "babel-node hypernova.js --presets es2015,react"
   },
   "author": "Noriaki Uchiyama",
   "license": "MIT",

これで、npm run hypernovaというコマンドが使用出来るようになります。

% npm run hypernova

> hypernova-getting-started-with-rails@1.0.0 hypernova /path/to/rails/app/hypernova-getting-started-with-rails
> babel-node hypernova.js --presets es2015,react

2016-07-03T09:17:15.470Z - info: Connected
{ port: 3030 }

正常にサービスが起動すると上記のようにConnectedと表示されます。

この状態で再度Railsアプリ http://localhost:3000/welcome/index へアクセスすると、表示内容やクリック時の動作はサーバサイドレンダリングを利用しないときと同じですが、ページのソースを確認すると以下のように直接HTMLにコンポーネントが出力され サーバサイドレンダリングを利用している ことが分かります。

<div data-hypernova-key="MyComponentjs"><div data-reactroot="" data-reactid="1" data-react-checksum="789933384"><p data-reactid="2"><!-- react-text: 3 -->カウント: <!-- /react-text --><!-- react-text: 4 -->0<!-- /react-text --><!-- react-text: 5 -->回<!-- /react-text --></p><button data-reactid="6">++</button><button data-reactid="7">--</button></div></div>
<script type="application/json" data-hypernova-key="MyComponentjs"><!--{}--></script>

という感じで、簡単にサーバサイドレンダリングを利用できるようになりました。

さいごに

本記事では、Reduxコンテナのサーバサイドレンダリングに対応したHypernovaのReact/Reduxバインディングであるnoriaki/hypernova-react-reduxを使った、シンプルなカウンターアプリをサーバサイドレンダリングで表示するまでのチュートリアルを解説しました。

Hypernovaを使えば、Railsでデータベース接続やAPIを実現し、フロントエンドはReact/Reduxによるシングルページアプリケーションを構築することも出来るでしょう。

ぜひ試してみてください。
もしお役に立ちそうなら「ストック」してもらえると嬉しいです。

なお、noriaki/hypernova-react-reduxでは、バグ発見や要望プルリクエストなどをお待ちしています。

参考


  1. Reduxでは、Store(すべてのStateを管理する単一のオブジェクト)を参照できる接続されたコンポーネント(Connected Component)をコンテナ(Container)と呼びます 

  2. reduxパッケージのcreateStore関数を内部で実行し、その結果を返す関数です http://redux.js.org/docs/basics/Store.html 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34