FluxもMVCも不要?コンポーネント指向Webプログラミング


(※2016/07追記: redux使ってみたら悪くなかったので宗旨替えしました。こちらの記事とかをどうぞ。http://qiita.com/uryyyyyyy/items/7d4b0ede3f2b973d6951

タイトルは半分くらい釣りですが、半分くらい本気です。

目的

Fluxわからん

Facebook公式の例の図を見て、「うっ、複雑だ。。。」って受け付けなかった人は僕だけじゃないと思ってます。

そのわりにはメリットもピンとこないし実装も乱立していて、正直『MVC』『オブジェクト指向』のようにバズワード化してる印象を受けています。

そのうち、「やっぱりお前らのFluxは間違ってる」とか、「お前の言うFluxはどこのFluxだよ」とか言い出すんじゃないかと思ってます。

そもそもFluxとかMVC要らなくね?

また、「ReactはViewしか提供しない」という意見がありますが、個人的にはそれで十分だと感じました。(こちらに書きました。React.jsはMVCのVじゃない。フレームワークだ。

なので、FluxやMVCありきではなく、まずは普通にReactだけで設計をしてみようじゃないか。そこで挙がる問題点と解決策を考えてみようじゃないか。

というところを意図しています。

僕はFluxの知識もないし、クライアントサイドの開発も経験も少ないので、

「こういうケースもあるよね」
「この設計じゃここで死ぬよね」
「それ亜流Fluxなんじゃね?」

などなどご指摘いただけると嬉しいです。

※ 以下、使わない用語

  • Flux
  • MVVM
  • ReactivePrograming
  • (WebのURLを用いた)Routing

先に結論

  • Fluxとか考えなくても問題なさそう。
  • クライアントサイドはView(DOM・それの動的な操作)が全て。
  • 業務ロジックとして独立させられるならそうしてもいい。

一般的なWebアプリの構成

超簡略化するとこう。

  • 親は子を知って(依存して)いて、子は親を知らない。

    • 子コンポーネントは再利用できるようになる。
  • 同じ子を複数の親が知ることはない。

    • これによって、複雑な画面においても依存関係がシンプルに保たれる。
  • 親から子にデータを渡す。

    • データのフローが統一されます。

コンポーネントという単語が曖昧ですが、Webアプリにおいては、DOM・振る舞い・データを持った何かです。

静的なHTMLであっても、DOMツリーは親子関係を持ってます。
form要素の中に書いた各valueが、submitボタンでpostされるとか、子から親にイベントがバブリングしていくとか。

以下、こういった設計を『親子コンポーネント指向』と呼んでおきます。

Webアプリの設計≒『親子コンポーネント指向』

古くからあるjQueryPluginなどを見てても、そのコンポーネント自身で独立してます。
必要な値があれば勝手にajaxで取ってくるとか、イベントが欲しければこのAPIからどうぞとか。

以前はMVCの時代があったようなのですが、最近は軒並み方向転換している印象です。

Angular 2系では、controllerやscopeを廃止して、Directive中心になると聞いています。MVCをなくしてコンポーネント間のやりとりだけにしようということかなと。

また、Facebookは「MVCはスケールしない」と言ってReactを開発してます。

また、WebComponentやRiot.js、Vue.jsあたりもHTMLの拡張なので『親子コンポーネント指向』といっていいでしょう。

Webでの『親子コンポーネント指向』においての問題

  • DOMの更新が重たい

    • 画面(DOM)の更新は重たいのでなるべくしたくない。
    • かといって更新が遅れると、内部状態とDOMで不整合が生じる。
    • それらを上手いこと調整しようとすると一気に複雑化する。
  • DOMを操作しにくい。

    • WebにおいてHTMLは静的な振る舞いだけで、動的な振る舞いを表すにはJSを使う必要がある。
    • jQueryは黒魔術的で設計がぶっ壊れる恐れが。。
    • (これの解決にHTMLを拡張させたのがGoogle系・JSを拡張させたのがFacebook系という認識です。。)

「LogicがDOM操作と密結合するのでは」という声に対しては、分離できるのなら汎用関数として抜き出せばいいし、分離できないのならViewの一部なんだと考えればいいと思う。

「Modelの扱いはどうするんだ」という声に対しては、そもそもModelを一元的に管理するとMVC時代に逆戻りするのでダメです。

(個人的にはModelもバズワードで意味が色々あるので使いたくないです。各コンポーネントが知るべき知るべき範囲のデータだけを状態として保持する形が理想だと思います。)

Reactの登場

以上の問題をReactはこう解決しました。

DOMの更新が重たい→仮想DOM

仮想DOMのメリットは、色々な人が言及しているように、「DOMの描画コストを考えなくていい」ことです。

具体的に言えば、「DOMの差分だけを変更することで、ほとんどの場合においてパフォーマンスを損ねることなく、最新の状態に応じた画面を0から生成できる。」とかでしょうか。

その結果、内部状態の管理も、各所でバラバラにデータを持つ必要がなく最新のデータのみを扱えば良いのでシンプルになります。

DOMを操作しにくい→仮想DOM・jsx

jsxという記法によって、JS側でわかりやすくDOMを扱うことができるようになりました。もちろん動的な差し替えも簡単です。
仮想DOMとリアルDOMの違いを意識することすらないでしょう。

その他、状態やイベントの扱い

問題として挙げてたところ以外は、上述の『親子コンポーネント指向』の考え方そのままです。

子は親に依存せず、親は子にデータを渡し、子は親へイベントをバブリングします。

(Reactが、props・stateというデータの扱い方を定めたのも個人的には大きいのですが、それはReactでなくてもできたことかなと。)

設計例

本当にReact + 『親子コンポーネント指向』だけで十分か、使いにくくないかを検討してみます。

それなりに複雑なチケット管理画面を例に取ります。

画面構成

1 GlobalなOverview
2 チケット一覧のOverview
3 チケットTitleをフィルタする文字列を入れるTextForm
4 チケットTitleのフィルタ後のList
5 チケット詳細のOverview
6 チケットのTitleのTextForm
7 チケットのAuthorのSelectBox
8 関連チケット一覧のOverview
9 関連チケットのTableList
10 関連チケットの追加Button
11 チケット詳細の保存Button

仕様(イベント一覧)

  1. 画面表示時に、ajaxで取得したチケット一覧が④に表示される。
  2. 画面表示時に、ajaxで取得したユーザー一覧が⑦に格納される。
  3. ③に文字を入れると、チケットのTitleをフィルタした結果を④に表示する。
  4. ④の要素一つをクリックすると、⑤にajaxで取得したそのチケットの詳細が表示される。
  5. チケット詳細に関連チケットがある場合、⑧はajaxでチケットのタイトルを取得し、⑨にそれを表示させる。
  6. そのチケットに関連チケットがある場合は、そのIDとTitleをajaxで取得して⑨に表示される。
  7. ⑩を押すと、関連チケットが追加される。(IDはランダムでいいや)
  8. ⑪を押すと、⑤に入ってる各データをajaxでpostする。

※ajaxの部分はlocalStrageでもファイルでもなんでもいい。

実装例

画面表示時に、ajaxで取得したチケット一覧が④に表示される。

④だけで完結してるイベント。④のコンポーネントがstateとしてチケット一覧を持つ。

画面表示時に、ajaxで取得したユーザー一覧が⑦に格納される。

⑦だけで完結してるイベント。⑦のコンポーネントがstateとしてユーザー一覧を持つ。

③に文字を入れると、チケットのTitleをフィルタした結果を④に表示する。

③に入れた文字列は、②のコンポーネントのstateとして、③と④に渡す。
④はpropsの値によって表示内容を書き換える。

④の要素一つをクリックすると、⑤にajaxで取得したそのチケットの詳細が表示される。

④でどのチケットIDが選ばれたかを①までバブリングする。①は、IDをstateとして持ち、⑤に渡す。

⑤はチケットIDを受け取り、ajax通信を行いチケット詳細を取得し、各データをstateとして保持し、⑥、⑦、⑧にデータを渡す。

⑥はTitleを受け取り、そのままtextformに表示する。
⑦はユーザーIDを受け取り、自身のユーザー一覧と一覧を照らしあわせてマッチするユーザーを表示する。

チケット詳細に関連チケットがある場合、⑧はajaxでチケットのタイトルを取得し、⑨にそれを表示させる。

⑧は関連チケットIDsを受け取り、ajax通信を行いチケットIDとタイトルのペアのデータを取得し、それをstateに持つ。そのペアのリストを⑨に渡す。

⑨は受け取ったペアのリストを表形式で表示する。stateは持たない。

⑩を押すと、関連チケットが追加される。

⑩を押すと、ランダムなチケットIDを含んだイベントが発火し、バブリングする。関連チケットIDsをstateに持つ⑤がそのイベントを処理し、⑧に関連チケットIDsを渡す。

⑪を押すと、⑤に入ってる各データをajaxでpostする。

⑪を押すとsaveイベントが発火し、⑤がそれを受け取って自身のstateのデータをpostする。

図解

つ、疲れた。。。

上記の設計の問題点

複数の階層を跨いでのデータ・イベントのやりとりが厄介

例えば、この図の⑩で発火したイベントを①で扱いたい場合、⑧⑤にもイベントの導線を張らなきゃいけない。(ただ、この手の問題はどんな設計でも遭遇する気がします。)

解決策としては、イベント・データを管理する何かを作り、そいつが複数の階層をまたぐ。平たく言えばグローバル変数みたいなもの。
ただ、そのままでは自由度が高すぎてカオスになるので、「この階層以下だけ」とかのスコープ切りたい。

最後に

Fluxって何が嬉しいのかわからないから誰か教えて。

参考資料