fluxパターンのシンプルな実装と解説:React 実践チュートリアル4

fluxパターンとは、要するにオブザーバーパターンの進化系の様なものである。

まず、オブザーバーパターンとは一言で言うと、データの状態の変化を検知し、その変更を受け取る仕組みのことである

データがあり、そのデータの変更が

の様な形で受け取っている時、それはオブザーバーパターンと呼んで良い。

fluxパターンとは、これにデータの流れ方のベストプラクティスを組み込んだものであり、
具体的には、データの変更要請 -> データの変更&保存 -> viewへの反映という一連の流れを分離し、かつ一方通行で行うべしというフレームワークである。

fluxパターンを使うと、データ層をviewから完全に分離することによって、複数のViewコンポーネント間での複雑なデータのやりとりがしやすくなるというメリットがある。

逆に言えば、シンプルなデータ構造のアプリケーションではfluxは必要ない。
あまり語られていないが、fluxを使うとどうしてもコードが複雑になるので、シンプルなデータ構造にはできるだけfluxは採用せず、component間でのpropsのやりとりにとどめておくべきというのもプロ目線でfluxを扱う上での重要な方針だ。

flux自体はすごくシンプルな発想だが、flux公式のこの図

がすごくわかりにくいので、fluxをなんだかすごい学習コストの高いものの様に勘違いする人が多い。
実際、これのせいで「最近のwebフロントエンドは難しい!」とか言われていたりするが、相変わらずwebフロントはそんなに難しい仕事では無いので安心してほしい。

結局はコードを見ないとよくわからないのである。では実際のコードで見てみよう

0. Memoアプリを作る(準備編)

今回は「fluxがなぜ必要なのか?」に焦点を当て、簡単なメモアプリを作っていく。

今回実装するのは次の機能

  • メモをinputに入力して追加ボタンを押したら下にリストが出る。

話をシンプルにするために変更・削除などの機能は無しとする。

前回の続きから、シンプルなコンポーネントを追加した状態でスタートする(react-bootstrapなんかもついでに追加しておいた)

http://localhost:3000/FluxSample.htmlにアクセスして次の様なページができればとりあえず準備は完了だ。

 

上のページはreact-bootstrapを使ってシンプルに作成した。
react-bootstrapはhttps://react-bootstrap.github.io/components.htmlにサンプルがあるが、bootstrapのパーツが全てComponentとして気軽に使える様になっており、今回の様なシンプルなアプリを試すには非常に便利だ。

ファイルはこんな感じに用意しておいた

 

1. fluxパターンを使わない実装

まずはfluxパターンを使わずに実装してみよう。

  • メモをinputに入力して追加ボタンを押したら下にリストが出る。

というのは、シンプルに実装するなら次の様な流れとなる

  1. Addボタンが押される
  2. InputのテキストをListに渡す

早速実装してみよう。

まずはAddMemoFormから

dom-parser内からShowMemoListComponentを探して、addMemoメソッドを呼び出す。
Reactのみでは親から子コンポーネントへのデータの受け渡ししかできないが、dom-parserを使えばこういった並列のデータ渡しもできるようになる。
Reactを使うときはAppといった巨大コンテナに包むのが普通と考えていた人はこのやり方も是非試して欲しい。設計の自由度が無限に広がるはずだ。

Reactにはrefという機能があり、renderメソッド内で

という様にref属性を使ってthis内の好きな名前のプロパティにElementをセットすることができ、

という様にしてElementが表示しているDOMを取得することができる。

次はShowMemoList.addMemoの実装だ。

this.state.memoを空の配列で初期化し、addMemoによってstate.memosにプッシュする。

あとはrenderメソッド内で現在のstateを表示しておく様にしておくだけだ。

2. fluxパターンを使わない実装の問題点

以上がReactのデータの受け渡し方の基本である。
コードもシンプルに収まるし、なるべくならこの実装で済ますのが基本だ。

しかし、追加、修正、削除、ソートなどの機能を追加していくには、問題が出てくる。

今回の場合、ShowMemoListComponent内でデータの操作をしているのが問題だ。

ShowMemoListは、本来メモのリストを表示するという機能のみに特化すべきであり、
渡されたデータが空白で無いか確認してmemosに追加するというデータ操作をしてしまっている。

これでは、’データの追加’という機能をShowListが持ってしまっており、より根本的な問題は正しいmemosの状態をShowMemoListしか知らないという問題である。

これは、こんな感じに現在のmemoを状態を表示するShowMemoSizeComponentを考えてみればわかりやすい。

早速実装してみよう。(一応ここまでのコードは4.3のタグで置いてある)

memosの本当の状態というのはAddMemoが実行された時(データ操作実行時)にしかわからないので、AddFormには次の様にShowMemoSize.addMemoを実行する行を追加することになる。

ShowMemoListComponentにアクセスしてmemosを取得することもできるが、その場合、データの同期という問題が出てくる。ShowMemoList内のmemosが変更されたことを知らなければShowMemoSizeはmemosの数を知ることができないのだ。

さらに、今回の実装は空文字の時に無視するという実装があるが、ShowMemoList内にその実装が漏れているため、空文字でもメモの数が増えて表示されてしまっている。

つまり、データの操作にまつわる実装を全てViewにも細かく実装しなければならず、バグの温床になりやすいのである。

この問題を解決するのがfluxパターンであり、この場合だとMemoActions.addMemosを発行し、それを各Componentが受け取るという仕組みを上の実装から分離するための仕組みである。

3. fluxの主要部品の解説とシンプルなflux実装

fluxというのは単なる概念であり、実装の仕方も複数ある。
だが、まずはきちんと概念を理解するために、一番複雑なfacebookの公式実装に近い形で自分で実装してみよう。概念さえわかれば後述するrefluxやreduxという現場で使われているライブラリでの実装もすんなり入っていけるはずだ。

再び、この図を見て欲しい

一つ一つ解説しながら実装していく

※ここまでのコードは4.3.1だが、npm install enum event-emitter –saveが必要

※ちなみに、以下の実装は[入門React]のコードをes6でわかりやすく、きちんと動く様に書き直したものである。シングルページではなく、あくまでReactをhtmlの一部として扱うように書いているので、facebookの実装よりシンプルになっている。

コードはこんな感じに置いてみた

 

■ Dispacher

Dispacherはdispatchとregisterの2つのメソッドを持ち、それぞれ

  • register: アプリケーションの初期化時にActionTypeに応じたStore内の対応メソッドを登録しておく。
  • dispatch: Action実行時、登録されたStore内のメソッドにActionで渡されたデータを渡す。

という役割がある。

es6で書くとこんな感じ。(この実装はサンプル用なので最低限)

シングルトンで実装するのでexportする時にnewしている。

dispatchの実装が少し長いが、パフォーマンス調整のための実装なだけで、registerで登録されたhandlers.callback (ActionTypeに応じてStore内のメソッドを発火)を実行しているだけである。

初期化処理は、コードの入り口に

こんな風に起動スクリプトを置いておく。

registerにMemoStore.addMemoを登録している様子が見えるが、これは上の図でいう右上の部分、
Dispatcher->callbacks->Storeを登録しているところである。

■ ActionCreators

ActionCreatorはその名の通りActionを作る場所だが、まずはコードを見ないとわかりにくい

これはComponent側から次のように呼び出される

上の図の左部分、ReactViews->UserInteractions->ActionCreator->Actions->Dispacherの実装がここに全てある。

Actionと呼ばれるものは

この部分で渡されているactionTypeとデータのセットである。これをDispatcherに渡すことで、Dispacherに渡して置いたStoreのメソッドが呼ばれる。

■ Store

Storeはデータの保存場所であり、唯一のデータ操作が可能な箇所である。

ActionCreatorからDispatcherを通じてStoreのaddMemoが呼び出されると、Store内でmemosのデータを変更し、変更されたことを通知する。

通知の仕方は一括してmemos全体であり、個別にmemoSizeを通知したり、変更箇所のみを通知したりはしない(することもできるが)。
MemoStoreはあくまで、memosの状態のみを管理し、memosが変更されたことをmemosごと通知する。

emitterというのはオブザーバーパターンでいうlistenerのことである。
addChangeListenerでstoreにコールバックを登録して置いて、Storeに変更が行われた際はそれをコールバックに渡す。

storeとviewの結びつけは以下のように行う

View内の余計なデータ操作が消え、memosの状態に応じてstateを変化させ、表示するというReact本来の姿に戻った。

AddMemoFormComponentもこのようにスッキリした見た目になっている。

4.reflux,reduxといったより簡単なフレームワークを使おう

上の実装は本当に簡単な生実装だが、実務で使うにあたっては必ずreduxかrefluxを使うことになるだろう。

そこで次回はrefluxについて、その次にreduxについての記事となる。

ここで簡単にそれぞれの特徴をまとめておくと

  • reflux:
    メリット::完全にアプリケーションから分離でき、複数のActionやStoreを気軽に設けることができる。また、シンプルな実装でわかりやすい。あまりfluxに慣れていないエンジニアが多く集まる職場向け
    デメリット:ドキュメントが少なく、人口も少ない。ネガキャンが多く今後淘汰される可能性もある(悲しい)
    また、プロジェクトが大きくなってくるとStoreが乱立して少々管理が面倒
  • redux:
    メリット:Storeがアプリケーション内に必ず一つであり、データ管理や設計がやりやすい。データの流れがシンプルになる。ドキュメントが充実。とにかく人口が多く、webでの情報も豊富。プラグインもようなものもたくさん出ている。
    デメリット:アプリケーションからの分離が難しい。シングルページアプリケーション向け(後の解説でこのブログのようなhtmlと共存する使い方でのreduxは解説する)。ドキュメントやサンプルコードが難解で離脱者が多数出る。オタクなプログラマーが集まる職場向け。

要するに私は個人的にreflux推しであり、このブログでもrefluxを推していくが、reduxの方が人口が多いのでしょうがなくreduxも解説する。

reduxは上のfacebookの生実装に近く、どうしてもコード量が多くなる。シンプルに一方通行のオブザーバーパターンが欲しいだけならrefluxで良い。

 

React 実践チュートリアル3:react-dom-parserを使ってサーバーサイドの値を受けとろう

react-dom-parserとは、<div data-react-component=”HelloWorldComponet” data-react-props=”message: ‘Hello World'”></div>のようにDOMから直接Reactのコンポーネントを呼び出すことができるすごいモジュールである。

前回の記事:React 実践チュートリアル2:React入門で、Reactがviewの状態をコントロールするだけの機能だということが明らかになったが、

この部分

どうもjquery臭くて違和感がないだろうか。

今回はこの臭いコードを消し、Reacを本来の役割だけに使うことにする。

1.react-dom-parserのインストール

まずはインストールから

index.jsはあくまでプログラムの入り口として機能させることにする

initParser.jsはこんな感じ

app/sample-htmlを書き換え

この部分でdata-react-componentで使うkeyを設定する。
今回は’HelloWorldComponent’で登録したが、別にSample.HelloWorldComponentで登録して呼び出してもいい
Componentを追加するたびにこのregisterに追加していくことで、data-react-component属性ですぐに呼び出すことができる。

また、componentへ渡すpropsをdata-react-propsで渡すことができるので、いちいちサーバーサイドで妙なscriptをhtmlに埋め込む必要がなくなり、サーバーサイドのviewがすっきりするメリットがある。

さらに、このparserを使うことで

というふうに、全てのComponentのマウントが終わったらというイベントを取得すことも出来る。
HTMLがComponentだらけになった時やfluxのStore内の初期化(別の記事で説明)にすさまじく便利だ

2.react-dom-parserの改造

ただし、実務で使っていくといくつか抜けている機能がある

ということはできるのに、

ということができない点だ(dojoでいうregister.findById)。
これができるのとできないのでは設計の柔軟さが段違いになるので、ラッパーを作ってしまおう(あとでfolkしてプルリクを出すかもしれない)

app/js/common/utils/domParser.js

仕組みとしては単純で、parser無いにregisteryByNameというプロパティを新規で作り、parse時に名前別に放り込んでいくようにしている。

これを使うようにinitParserを変更すれば完成だ

 

3.現在のHelloWorldがクリックされた回数を表示するボタンを作ろう

せっかくなので、このparser.findComponentByNameの実際の使い方を見てみよう

ShowHelloWorldCounterButtonComponentを作り、クリックしたらHelloWorldComponent.state.counterを表示するという簡単なComponentを作る。

ところで、空のComponentを作るのは手間では無いだろうか。
ちょっと手間をかけて空のComponentを生成するgulpタスクを作ってみる(ついでにsampleのhtmlも作れるようにしておく)

gulp/tasks/create.js

依存モジュールのインストール

npm install gulp-replace yargs --save-dev

configファイルの書き換え

gulp/templates/にテンプレートを配置

実行結果

gulpをきちんと整備しておくと、こうしたちょっとしたタスクもすぐに用意できて便利だ。

実行したらinitParserにパスを通し、sample-htmlに配置する

ShowHelloWorldCounterButtonComponent.jsもサクっと実装してしまおう

Hello Worldをクリックしてビックリマークをクリックしてからボタンを押すと、クリックされた回数が表示される。

この例ではReactの良さが全く伝わら無いと思うが、少なくともComponent感で値の読み取りが簡単にできることは確認できると思う。

これで設計の幅が大幅に広がるはずだが、Reactの本来の使い方としては、次回扱うFluxパターンを使うほうがよろしい。

次回:→fluxパターンのシンプルな実装と解説:React 実践チュートリアル4

 

React 実践チュートリアル2:React入門

Reactで何ができるか?というのは、実際にコードを見るのが一番早い

前回の記事で最低限の環境構築は作っておいたので、前回の続きからスタートする。

済んでない人は以下を実行して開発環境を作ってください

1.初めてのReact Component

前回書いたのはこんなコードである

これは#hello-worldに

を表示せよというコードである。

まずはコンポーネント化ということから始めよう

app/js/samples/component/HelloWorldComponent.jsを作成する

これをindex.jsでインポートして使ってみる

 

表示上は変わらないが、

<h1>….</h1>

の部分をコンポーネントとして抜き出すことができた。

これだけでは面白くないので、クリックするとHello, world のビックリマークが増えて行くようにしよう

とりあえずはonClickをh1にバインドする

とすることで、h1のonClick属性にthis.handleOnClickをバインドできる

handle<イベント名>というメソッド名でなくてもonClickという名前でも動くが、Reactではイベントに関連付けるメソッドは慣習としてhandle<イベント名>という名前が使われているのでそれに倣う。

このhandleOnClickメソッドはあくまで’render内のonClickを扱う’という役割なので、onClickという名前だとcomponent自体をクリックしているイメージになってしまうからだと思う。

2.JSXとは何か?

ここでJSXについて説明しよう。

jsxとは要するに、javascript内にHTMLのようなタグを書くことができる拡張言語である。

という部分もJSXであるし、

という部分もJSXである。

JSXで宣言した部分は指定されたComponentからElementと呼ばれる要素を生成する
h1といったhtmlで定義されているタグは全てReactがあらかじめComponentを作ってくれているが、HelloWorldComponentのように自分のオリジナルComponentを作ることもできる。
Elementはレンダリングが必要な時にComponent内のrenderメソッドを呼び、その内容を外に出力する。

JSXではHTMLタグと同じように属性が指定でき、

のように属性に値を入れると、HelloWorldComponentのthis.props.nameに値をセットすることができる

<HelloWorldComponent>Test String</HelloWorldComponent>
とタグの中身に入れると、this.props.childrenという特殊なプロパティにTest Stringをセットすることができる。

つまり

というのは、h1というElementのthis.props.onClick属性にthis.handleOnClickをセットし、this.props.childrenにHello, world!をセットしているということである。

属性に値を直接セットするときは
name=’James Brown’
変数を入れたい場合は
name={name}
のように{ }を使って囲む

属性を一気にセットしたい場合は

というようにobjectにまとめて一気にセットすることができる。

childrenにはもちろんDOMも入れることができる

3.PropsとStateを使う

propsの話が出たので、HelloWorldComponentをpropsにmessageを渡すように変更しよう

上のようにstatic get(readOnlyのstaticプロパティ)としてpropTypesとdefaultProps(es6以前でいうgetDefaultProps)を指定することでpropsの入力をコントロールすることができる。

propsは完全にread-onlyなので、コンストラクタ内で
this.props.message = ”;
のように初期化はできないので注意しよう

Component内で変化する値にはstateを使う(es6以前はgetInitialState)
今回はクリックされた数をカウントしてその数だけビックリマークを増やしたいので、クリックされた数をstateにセットしよう

stateはコンストラクタで初期化する。

hamdleOnClickにthisを注入しているのは、JSXに注入する際にスコープがthisから外れてしまってthis.setStateにアクセスできないためである。

この部分でthis.state.countを変更しているが、
this.state.count +=  this.state.count;
のように直接変更してはいけない
stateの変更は必ずsetStateを使う。

setStateはrenderメソッドをリフレッシュするので、これによってstateがrenderに反映されることになっている。

まとめると
props: Componentが生成される時に渡される値の集まり
state: Componentのrender内で使われる状態の集まり

である。

びっくりするかもしれないが、これがReactで使われる機能の全てである

要するに、Reactとは

  • Componentの状態を表示する

という機能が全てであり、余計なことは一切しない。凄まじくシンプルなViewオンリーのフレームワークである。
あとは入れ子になったComponentへの参照やComponentのライフサイクルの機能が付いているだけである。

ルーティングはサーバーに任せたり、AngularJSのviewだけReactを使ったり、 fluxパターンを使ったりと自由に構成を選ぶことができる。

このことを踏まえると、componentをDOMに結びつけるこの部分

は、よく見るとReactDOMという別のライブラリとなっているのがわかる。
DOMへの結びつけはReact本体の機能では無いのだ。

次回の記事ではreact-dom-parserを使って、この

  • DomにComponentを結びつける

という部分も削除することによって、

  • Componentの状態を表示する

という一点に集中できる環境にする。

次の記事→React 実践チュートリアル3:react-dom-parserを使ってサーバーサイドの値を受けとろう