LoginSignup
156

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-21

(※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アプリの構成

超簡略化するとこう。

Screenshot from 2015-02-21 18:17:58.png

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

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

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

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

コンポーネントという単語が曖昧ですが、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 + 『親子コンポーネント指向』だけで十分か、使いにくくないかを検討してみます。

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

画面構成

Screenshot from 2015-02-21 18:35:48.png

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する。

図解

つ、疲れた。。。

Screenshot from 2015-02-21 18:37:48.png

上記の設計の問題点

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

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

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

最後に

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

参考資料

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
156