Rails
flux
turbolinks
reactjs
React.jsDay 25

react(flux)とrails(turbolinks)の親和性

More than 1 year has passed since last update.

1. 概要

この記事はQiita React.js Advent Calendar 2015の25日目に投稿させて頂いた記事です.
react(flux)とrails(turbolinks)を組み合わせるとどのような利点があるのか,ということについて書きたいと思います.
端的に言うと複数ページに渡るものでどういう風にreactの良さを活かしていくか,という記事になります.

2. turbolinksについて

turbolinksはajaxを用いてページ遷移を行ってくれる機能で,Rails4からデフォルト搭載されています.
動作としてはajaxでDOMだけを更新し,jsやcssの取得し直しはしない,といった具合になっています.

この機能は,様々な問題も発生するのですが(例えばTwitterのwidgetが発火しないとか),react(flux)とはとても相性が良い機能です.
fluxを用いた場合,ajax(fetch)で取得されたデータはStoreに保存され,それに基いて描画を行うため,ページ遷移時のjsの読み直しはないほうがありがたいためです.
こちらはjsがプリコンパイルされ,1つのファイル(どのページでも同じ)になっていることが前提の話となりますが,railsの場合はそのようになっています.

更に詳細を知りたい方は,以下の記事が参考になったのでお勧めしておきます.

3. 具体的にやりたいこと

私が具体的にやりたかったことは,Storeの情報を維持したままページ遷移することです.
通常であればreact-routerを使うところなのですが,railsにデフォルトで入っているturbolinksを使うことで同じようなことが実現できます.

どのページから見られてもWebページに必要なコンテンツを非同期でStoreに格納しておき,
ユーザに対してページ遷移の時間で不満を感じさせない,ということがやりたいことでした.
例えばコントローラ側の処理に1000msかかるような重いページであれば,非同期でロードしておき,renderコストのみに抑えるといったことです.
最初に重いページを開かれた場合はどうしようもないのですが,トップから重いページに移動する際などに役立つと思います.

4. TODO App

実際に簡単なTODO Appを作りました.

ob.gif

fetchでGETしているtodos.jsonがStoreにデータを格納しているところです.
最初のページロード以外では一覧を取得していないということが見にくいですが,わかると思います.
このようにturbolinksを上手く使うとAPIを叩く量も減らすことができ,非常に良いなと感じました.

このAppではTODOのstateが空だった時のみ現状の全てのTODOを取得し,それ以外の時は取得し直しは一切行わないようにしています.

TodoActionCreators.fetch() if Object.keys(@state.todos).length is 0

利用した主なgemは,

  • react-rails
  • bower-rails
  • sprockets-coffee-react

の3点です.

react-rails

railsでreactを使う際に便利なgemです.
server側でのprerenderができたり,スネーク<=>キャメルの変数名の変換や,development/productionモードなどの切り替えもできます.

bower-rails

フロント系ライブラリの管理を行ってくれます.
rails-assetsと違ってフロント系ライブラリで画像などが使われていた場合にも対応しているので,
productionを踏まえて利用するならbower-rails一択かなと思っています.

今回はfluxとeventemitterをインストールするために利用します.

sprockets-coffee-react

cjsxでreactを書くためのgemです.
jsxのまま書きたいという方は不要かもしれません.

デフォルトでもcoffeescriptの対応はされているのですが,render部分を

  render: ->
    `<div></div>`

といった感じで`で囲わないといけないのが面倒なので使っています.

5. 実際色々作るための環境準備

Rails 4.2.5,Ruby 2.2.3での環境準備となります.
まずは,railsをnewしてからライブラリ周りの準備を整えていきます.

rails new flux-rails -T --skip-bundle
vim Gemfile

Gemfileを開いたら以下の4つを追加します.
slim以外のテンプレートエンジンを使いたい方は適宜置き換えて下さい.

gem 'react-rails'
gem 'sprockets-coffee-react'
gem 'bower-rails'
gem 'slim-rails'

追加した後はbundle install

bundle install -j4 --path vendor/bundle

次に,フロント系ライブラリの準備をします.

rails g bower_rails:initialize
vim Bowerfile

Bowerfileにfluxとeventemitterを追加.
reduxなどもありますが,公式のfluxを利用しています.
今回はeventemitter2の方を利用します.

asset 'flux'
asset 'eventemitter2'

終わった後はrake bower:installでインストール完了です.
最後に,jsファイルを読み込ませます.
react系の読み込みは,turbolinksの後に書かないとページ遷移時に正しくロードされないので注意が必要です.

app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require react
//= require react_ujs
//= require flux/dist/Flux
//= require eventemitter2/lib/eventemitter2
//= require_tree ./constants
//= require_tree ./dispatcher
//= require_tree ./actions
//= require_tree ./stores
//= require_tree ./components

require_treeではディレクトリがないとエラーが出るのでとりあえず,ディレクトリも生成しておきます.

cd app/assets/javascripts/
mkdir constants
mkdir dispatcher
mkdir actions
mkdir stores
mkdir components

これで環境構築としては終了です.
後は普通にreactを書くときのように記述していけばOKです.

6. react-railsの利用しているモノ

componentをrender

通常のrenderは

= react_component('HelloMessage', {name: 'John'})

サーバサイドでrenderingさせたい場合はこんな感じ.

= react_component('HelloMessage', {name: 'John'}, {prerender: true})

name: 'John' はpropsに渡る値となります.
何のtagでrenderするか,class名は何にするか,というのもここで指定が行えます.

キャメル<=>スネークに変数名を変換

config/application.rbconfig.react.camelize_props = trueという記述をしておくと,
キャメル<=>スネークの変数名変換を自動で行ってくれます.
todo_id: 4をpropsで渡すと,@props.todoId => 4といった感じにしてくれ,地味に便利です.

controllerでrender

class TodoController < ApplicationController
  def index
    @todos = Todo.all
    render component: 'TodoList', props: { todos: @todos }, tag: 'span', class: 'todo'
  end
end

コントローラ側でもサーバサイドでのrenderingが行えます.

その他として,自分はcoffeeで書いているのでよく知りませんが,ES6で書くこともできるようです.

7. まとめ

  • turbolinksを使うとrailsのrailsに乗ったままreact(flux)が利用できる
  • react-railsは便利