#はじめに
Webサービスを作っている際に、サービスの規模が大きくなるにつれてviewで用いているjsの記述が煩雑で見辛く、保守が難しくなってきたなーと思った経験ありませんか?
個人で開発している分には良いのですが、いざ他の人に改修を頼もうと思った際に、このDOMの操作に関わるjsはこのファイルに書いていて、あのDOMの操作に関わるjsのファイルはあっちのファイルに書いていて...
なんでこのjsはhtmlファイルに直接書いてあるのに、こっちの部分はファイルに分けてあるの?と。
凄く極端な例で示しましたが、Webサービスの開発に携わられたことがあれば少なからず似たような経験をしてきたことがあるのではないでしょうか?
そのやりとり面倒くさいですし、開発の引き継ぎを託された側も修正に必要な関連ファイルをいちいち探すの大変ですし、そもそもjsの記述が多くなればなるほど保守もしづらくなってきますよね。
そんな状態に陥る前にチームでルールを決めてしまえば良いのですが、既に縺れ始めたサービスにルールを適用し、併せて修正していくことは非常に労力がかかりますよね。
今回、この記事で紹介させて頂く、React.jsとRefluxフレームワークを知ることは、そんなあなたのチームの悩みをシンプルに解決してくれるきっかけの1つになるかもしれません。
##この記事の対象者
- React.jsをサービスに導入すること検討している方
- View周辺のルール作りに悩んでいる方
- View周辺のリファクタリングを考えている方
- Virtual DOMに興味があるけど触ったことが無い方
- DOMの操作が頻繁に必要になりそうで、なおかつ規模が大きくなりそうなサービスを考えている方
※ javascriptのフレームワークには他にも、Backbone.jsやvue.js、Angular.jsなどがあります。
以下のサイトでは代表的なjsのフレームワークの特徴を分かりやすくまとめてくださっています。
React.js以外にも自分のサービスの適正等を考慮した上でフレームワークを選定することが出来ると思うので、宜しければ併せてご覧ください。
#さっそく本題
##React.js
React.jsはFacebook社製のOSSなjavascriptライブラリです。
このライブラリは大きな特徴として3つのものを持っています。
###1. JUST THE UI
React.jsはMVCデザインパターンを元に実装されているサービスのViewの部分を請け負います。
コンポーネントと呼ばれる最小機能単位で1つのファイルの実装を行うわれ、他の技術にも依存しないので、既存のシステムでも機能単位でテスト的に導入することが可能です。
###2. Virtual DOM
Virtual DOMについては、なぜ仮想DOMという概念が俺達の魂を震えさせるのかの記事の方で凄く詳しく、情熱的に語られているので参考にさせていただきました。本記事ではその中の該当部分を引用させていただきます。
##なぜVirtual DOMか
結論から言うと、「設計と速度が両立する」から。以下その理由。
HTMLとはツリー構造であり、2つのツリー構造のdiffを算出して、それをDOMにpatchするアクションを作れば、常に最小のコストで状態遷移を表現できるよね、ってのがVirtual DOMという発想のスタート地点となります。
- State A :
<div class='foo'>aaa</div>
- State B :
<div class='foo'>bbb</div>
- 作られるdiff: fooのtextの-aaa+bbb
- 差を埋めるpatch:
node.querySelector('.foo').innerHTML = 'bbb'
(これは雑な表現)
このHTMLの生成する元となるツリー構造は、生のDOM(HTMLのインスタンス)である必要はなく、DOMと1対1に対応する単純な構造体で表現し、それを仮想DOMと呼びます。
Virtual DOM実装といった場合、仮想DOMの構造体表現と、それを用いたdiff/patchアルゴリズムを指します。
(この文章の引用元: http://qiita.com/mizchi/items/4d25bc26def1719d52e6)
###3.DATA FLOW
React.jsでは、データの変更に応じてUIの変更が行われる単方向のデーターバインディングを採用しています。
これにより、例えばjQueryでの実装の場合には直接DOMを操作してパラメータ取得した後、Viewをごにょごにょしていたのが、Reactでは、textareaなどUIのデータの変更が行われたタイミングで予め設定されたルールに従ってViewが再描画されます。
##Reflux
###Refluxのベースになっているfluxアーキテクチャ
Viewを責務しているReact.jsは、あまりに自由な組み合わせで機能・処理を実装できるため、コードが複雑化してしまうというリスクがありました。
そのリスクの解決策として、「より予測可能な形でコードを構造化」することが求められました。そこで、Facebook社がReactを用いる際のルール枠組みとしてFluxアーキテクチャを同時に提唱しました。
(参照元: http://www.infoq.com/news/2014/05/facebook-mvc-flux)
このようにデータの流れを単方向にすることでシンプルに追いやすくしています。
上記の図のそれぞれの役割を説明すると、以下のようになります。
- Store: アプリケーションの全てのデータを保持している。
- Action: Viewで発生した入力を受け取り、担当するDispatcherへ処理を受け渡す。
- Dispatcher: MVCアーキテクチャで言う所のControllerを担当していて、あるActionが実行されたときのStoreがどう更新されるべきかを決定している。
- View: Storeの内容によって画面を描画し、ユーザからの入力を受け付ける。
###Refluxアーキテクチャ
Refluxアーキテクチャは、fluxアーキテクチャの思想を継承して、クライアントサイドとサーバサイドの両方でよりシンプルで簡単にwebアプリケーションを実装できることを目指して作られました。
ActionとStoreがあり、データの流れが単方向であるという部分はFluxアーキテクチャの思想が継承されています。
一方で、Storeから直接ActionをListenableでlistenできるのでDispatcherを必要としていません。
以下が概要図になります。Refluxのgithub内の図を引用させて頂いています。
╔═════════╗ ╔════════╗ ╔═════════════════╗
║ Actions ║──────>║ Stores ║──────>║ View Components ║
╚═════════╝ ╚════════╝ ╚═════════════════╝
^ │
└──────────────────────────────────────┘
Fluxに比べて簡略化されていることがわかります。
またDispatcherやconstantsなど関連ファイルの数も少なくなるのでコードの生産性があがりますね!
#実装例
恐らくこれまでの説明だけだと何がなんだか特徴もわかりづらいと思うので、以下で、ブログのタイトルと説明文をpostするフォームの実装を行い実際にソースコードで追いながら説明します。
本来この程度の実装であれば別のフレームワークを用いるべきですが、雰囲気を掴む為にあえて簡単な方法を用いて実装を行います。
##React.jsとRefluxを用いた実装例
###1.導入(基本的にgithub上のそれぞれのコマンドを実行)
それぞれの導入に関しては事例等がたくさん上がっているので今回の記事では省略化させていただきます。
githubの説明を参考にお好みのパッケージ管理に合わせたインストール方法をお試しください。
Refluxのインストールまで完了すると、ディレクトリ配下に、app.jsxファイルと、actions・stores・componentsのディレクトリができていると思われます。
app.min.jsを指定しているのは、jsxをjsにコンパイルする際に圧縮しているからです。
圧縮は小規模ファイルでは必要ないかもしれないですが、大規模になるのであればしたほうが良いと思います。
インストールが終了したらまず以下のようにベースとなる新規のHTMLドキュメントを作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Create Author</title>
</head>
<body>
<div id="article"></div>
<script type="text/javascript" src="./app.min.js"></script>
</body>
</html>
###2.必要機能の設計
Componentの記述を行う前に、まず必要機能の設計を行います。
今回だと、ブログのタイトルと説明文を実装したいので、Viewに必要なのは、ブログのタイトルと説明文を入力できるテキストエリア、あとsubmitボタン、resetボタンが必要です。
以下が従来のhtmlで書かれたフォームになります。Componentでは、これをベースにReact風に書き換えて使用します。
<div>
<div className="form-group">
<label>記事タイトル</label>
<input type="text" className="form-control" placeholder="記事タイトルを入力してください" />
</div>
<div className="form-group">
<label>説明文(ディスクリプション)</label>
<input type="text" className="form-control" placeholder="ディスクリプションを入力してください" />
</div>
<div>
<input type="submit" value="送信する">
<input type="reset" value="リセット">
</div>
</div>
次にactionsの設計ですが、今回はresetボタンを押されたときにテキストフォームの中身を空にするactionと、submitボタンを押されたときに登録処理を行うactionが必要になります。
なので、actionは以下のように定義します。
- Create: 記事名と説明文を登録
- Reset: textareaの記述をリセットする
最後に、Storesですが今回のデータで必要になるのは記事名と説明文なので以下のように状態を定義します。
- name: 記事名
- description: 説明文
###3.設計に従い実装
なお以下の実装は全てmodule管理をしています。
まず、最初にComponent(View部分)の実装を行います。
var React = require("react")
var Reflux = require("reflux")
// actionを定義しているファイルの読み込み
var ArticleActions = require("../stores/Article.js");
// storeファイルの読み込み
var ArticleStore = require('../stores/ArticleStore.js');
// 描画処理を行うclassの作成
var ArticleNameSetting = React.createClass({
// ArticleStoreで保有しているデータをarticleとして保有する。
mixins: [Reflux.connect(ArticleStore, "article")],
// タイトルが変更された時に行われる処理
handleInputTitle: function(event) {
if (this.state.article.title !== event.target.value) {
this.state.article.title = event.target.value;
// ここでデータの変更が行われるので再描画される
this.setState(this.state.article);
}
},
// タイトルが変更された時に行われる処理
handleInputDescription: function(event) {
if (this.state.article.description !== event.target.value) {
this.state.article.description = event.target.value;
this.setState(this.state.article);
}
},
reset: function() {
// Resetボタンが押された時のフォームを空欄にする処理
/* 仮想DOMの値を直接変更することもできる。
その際はフォームにrefという要素を指定する。 */
ArticleActions.Reset();
},
regist: function() {
// 登録ボタンが押された時の処理
ArticleActions.Create();
},
render: function(){
return (
<div>
<div>
<label className="col-md-12">記事タイトル</label>
<input className="form-control" placeholder="記事タイトルを入力してください"
value={this.state.article.title} onBlur={this.handleInputTitle} />
</div>
<div>
<label>説明文(ディスクリプション)</label>
<textarea className="form-control" placeholder="ディスクリプションを入力してください"
value={this.state.article.description} onBlur={this.handleInputDescription} />
</div>
</div>
<div>
<button onClick={this.reset}>リセット</button>
<button onClick={this.regist}>登録</button>
</div>
);
});
module.exports = Article;
次に設計に従いActionsの実装
var Reflux = require('reflux');
var ArticleActions = Reflux.createActions([
"Create",
"Reset",
]);
module.exports = ArticleActions;
次にStoresの実装を行います。
Storesでは初期状態の他に、actionで定義したメソッドの実装も行います。
var Reflux = require('reflux');
var ArticleActions = require('../actions/ArticleActions.js');
var ArticleStore = Reflux.createStore({
// listenableで呼び出し元のactionを指定する
listenable: [ArticleActions],
//stateに入る初期状態を定義
getInitialState: function(){
this.article = {
name: "",
description: ""
},
return this.article;
},
// メソッドを定義する時はactionに指定したものの頭に"on"を付けます。
onCreate: function() {
//この中でapiなどを用いてpostする処理を記述する。
},
onReset: function() {
// 保有するstateの初期化(ここもsetState()と同じでstateの内容に合わせて再描画処理が行われる)
this.trigger(this.getInitialState());
}
});
module.exports = ArticleStore;
最後にhtmlとcomponentを結びつけてあげます。
var React = require('react');
var Article = require('./components/Article.jsx');
var article_root = document.getElementById('article');
React.render( <Article />, article_root );
以上がReactとRefluxを用いた大体の実装です!
書き終わってから気づいたのですが、textareaは直接書き込み可能なのでstateによって内容を変化するところを見せることができませんでしたOrz
今度もっと元気がある時にこの記事をcheckboxなどもっとわかりやすい例を用いたものに編集したいと思います。
#さいごに
新人プログラマ応援ということで僭越ながら15卒が記事を書かせて頂きました。
色々調べて書きたいことを書いているうちにまとまりのない文章になってしまい伝えたいことがぶれてしまいましたが、この記事の最も伝えたかったことは、Viewの1つの可能性にReact.jsいかがですか?ということです。
私自身、最近までほぼほぼjavascript初心者だったので、書いている内容に甘いところが多く、疑問点やご指摘等あるかと思いますのでコメント欄に残して頂けると嬉しいです!
皆様のサービスのViewが快適なものとなるよう、この記事がお手伝いできれば幸いです!
あと、以下のサイトは私がReactを勉強する際や、今回の記事を作成するにあたり参考にさせて頂きました。
ありがとうございます!
- Reactチュートリアル
- React Native - Reflux
- 一人React Native Advent Calendar 2015
- なぜ仮想DOMという概念が俺達の魂を震えさせるのか
- JSといえばjQueryだったWebデザイナーが、Reactを1年間使って感じたメリット
また、Reactの思想に沿って実装したより詳しい例を同期が紹介しているのでこちらもよろしければご覧ください。
最後まで見ていただきありがとうございました!