この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
丹内です。
最近ReactJSを触り始めました。
掲題の通り、Railsをバックエンドにした場合のFluxを書いてみたのでまとめます。
このエントリの参考URLにあるリポジトリを参考にしました。
バックエンド
普通のRailsアプリです。Scaffoldをベースに、フロントエンドを書いていきます。
db/migtate/create_books.rb
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.string :title
t.timestamps null: false
end
end
end
BooksController#index
今回、ブラウザで/booksにアクセスした時に、最初に空っぽのHTMLを返し、ReactJSでAjaxリクエストによってデータを取得しDOMを構築する方法をとったため、indexアクションにJSONの場合を追加しています。
def index
@books = Book.all
respond_to do |format|
format.html { render :index }
format.json { render json: @books }
end
end
実装
rails newでアプリを生成したあと、Railsアプリ直下にclientという名前でディレクトリを作り、そこで適当にクライアントサイド開発環境を作ります。
app/books/index.html.slim
HTML(テンプレートエンジンはSlimです)は、これだけです。
h1 Listing books
#content
ここからAjaxで取得したデータをDOMとしてくっつけます。
client/assets/javascripts/App.jsx
ここがクライアントサイドのエントリポイントとなります。つまり、ブラウザでhttp://localhost:3000/books
にアクセスしてページを読み込んだ直後にここから処理が始まります。
import $ from 'jquery';
import React from 'react';
import BookBox from './components/BookBox';
$(function onLoad() {
function render() {
React.render(
<div>
<BookBox url='books.json'/>
</div>,
document.getElementById('content')
);
}
render();
});
このBookBoxは別のReactClassとして定義してあり、それをimportして呼び出すだけの処理です。
client/assets/javascripts/components/BookBox.jsx
ReactJSの記述が本格的に始まります。まずは諸々のライフサイクルを定義しています。
import React from 'react';
import BookStore from '../stores/BookStore';
import BookActions from '../actions/BookActions';
import BookList from '../components/BookList';
const BookBox = React.createClass({
displayName: 'BookBox',
propTypes: {
url: React.PropTypes.string.isRequired
},
getStoreState() {
return {
books: BookStore.getState()
};
},
getInitialState() {
return this.getStoreState();
},
componentDidMount() {
BookStore.listen(this.onChange);
BookActions.fetchBooks(this.props.url, true);
},
componentWillUnmount() {
BookStore.unlisten(this.onChange);
},
onChange() {
this.setState(this.getStoreState());
},
render() {
return (
<div className="bookBox container">
<BookList books={ this.state.books.books }/>
</div>
);
}
});
export default BookBox;
conponentDidMount()
で呼び出したBookStore.fetchBooks()
でAjaxリクエストを実行し、renderでその結果を参照しています。
client/assets/javascripts/actions/BookActions.js
ここからはReactJSではなくAltの領域になります。まずはActionです。
fetchBooks
でAjaxリクエストを送り、コールバックでこのクラスのupdateBooks
を呼んでいます。
少し分かりにくいのですが、その中のthis.dispatch
でStoreと連携させています。
import alt from '../FluxAlt';
import $ from 'jquery';
class BookActions {
fetchBooks(url) {
$.ajax({
url: url,
dataType: 'json'
}).then(
(books) => this.actions.updateBooks(books),
(errorMessage) => this.actions.updateBooksError(errorMessage)
);
}
updateBooks(books) {
this.dispatch(books);
}
updateBooksError(errorMessage) {
this.dispatch(errorMessage);
}
}
export default alt.createActions(BookActions);
ここでimportしているFluxAlt
は、以下のようなユーティリティです。(client/assets/javascripts/FluxAlt.js)
import Alt from 'alt';
const alt = new Alt();
export default alt;
client/assets/javascripts/stores/BookStore.js
ここもAltです。Fluxで言うところのStoreです。
ポイントはconstructor
内のthis.bindListeners
で、ここでActionからdispatchした関数とStore内の関数を結びつけています。
import alt from '../FluxAlt';
import BookActions from '../actions/BookActions';
class BookStore {
constructor() {
this.books = [];
this.errorMessage = null;
this.bindListeners({
handleFetchBooks: BookActions.FETCH_BOOKS,
handleUpdateBooks: BookActions.UPDATE_BOOKS,
handleUpdateBooksError: BookActions.UPDATE_BOOKS_ERROR
});
}
handleFetchBooks() {
return false;
}
handleUpdateBooks(books) {
this.books = books;
this.errorMessage = null;
}
handleUpdateBooksError(errorMessage) {
this.errorMessage = errorMessage;
}
}
export default alt.createStore(BookStore, 'BookStore');
その他コンポーネント
以下、client/assets/javascripts/components/BookBox.jsx
から読んでいるComponentです。
まずはclient/assets/javascripts/components/BookList.jsx
import React from 'react';
import Book from './Book';
const BookList = React.createClass({
displayName: 'BookList',
propTypes: {
books: React.PropTypes.array
},
render() {
const data = this.props.books;
const bookNodes = data.map((book, index) => {
return(
<Book title={book.title} key={index}/>
);
});
return (
<div className="bookList">
{ bookNodes }
</div>
)
}
});
export default BookList;
そしてBookListの中で読んでいるclient/assets/javascripts/components/Book.jsx
import React from 'react';
const Book = React.createClass({
displayName: 'Book',
propTypes: {
title: React.PropTypes.string.isRequired
},
render() {
return(
<ul>{this.props.title}</ul>
);
}
});
export default Book;
まとめ
RailsアプリにAjaxリクエストを送るFluxを、Altを使って実装してみました。
クライアントサイドにはまだ入門したてなのですが、Node.JSを使ったフロントエンド環境構築だけで数日かかってしまいました。
RailsのAsset Pipelineにいい感じに載せられて開発効率を上げられるようにアセット管理をしつつ、もっとフロントエンド開発をして行きたいです。