JSでMVCパターンを使ったデモを作った - jQuery編 -
背景
私は、デジタルハリウッドである程度フロントエンド系のスキルを身につけて卒業しその後なんやかんやあってWeb系の制作会社に入りました。
デジハリにいた頃が2011-2012年の辺りで、それから制作会社に入ったのが2015年と結構な開きがあり、そして、その間scssを使ったコンパイル、browserifyといったJSビルドツールや、果てはフロントエンド全体での開発環境等様々なトピックがありました。そもそも2011年頃から現場レベルでも意識していたところなのかもしれませんが、専門学校上がりで実務レベルがなかった自分にとっては大量の情報量を受ける良い洗礼でもありました。
そして現在、開発環境とかビルドツールの知識も得たものの、分かったつもりになっていたり、名前だけ聞いてそのままだったりする事柄もあるため、以下の点を踏まえてブログに書き止めようと思いました。
1. JSの基礎領域をターゲットに自身の振り返りを行う
2. 最近取り上げられている or 今後注目される技術分野を調べてみる
1.は、普段のJS開発だと、アニメーションはTweenMax、AjaxならjQuery、とライブラリ頼りで進めているところがありました。
コアな部分の実装にフォーカスするために他の事柄にはライブラリで捌く判断も大切ですし、車輪の再発明的なものにするつもりでもないのですが、やはりどういうロジックで動かすのかを自分の中でも落とし込んでいかないと、そこで止まってしまう気がして。
なので、こういったJSの基礎的な領域をまずは自分なりにロジック立てて作ってみつつ既存ライブラリのコードから理解し、ひいてはベストな実装方法につなげていけるといいなと思ったりしています。
2.については、最早フロントエンドなら常識なのであろうReactのような仮想DOMを扱うライブラリやら、WebVR・AR、今着目されつつあるProgressive Web Appsといった新し目の技術領域を調べて得たことをメモなりまとめていこうと思います。
こちらは汎用的な1.に比べ固有なケースで使われ用途も限られてくるでしょうが、フロントエンドとしては放ってはおけない新しい分野なので逐次勉強してみようと考えています。また、フロントエンドに限らずUnityなりインタラクティブ系のツールに関する事柄もターゲットに据えていくつもりです。
今回は、1.の領域で「MVCパターン」の事柄を整理しようと思います。とはいえ、現時点では現場で学んだ点の整理にフォーカスをおいてメモしたものになります。書籍やサイトで得た事柄も追って追加していこうと思います。
MVCとは
O'REILLY の JavaScriptデザインパターン によると、
「MVC(モデル・ビュー・コントローラ)と、役割を分離させることで良いアプリケーションの構成を目指すアーキテクチャデザインパターン」のことを指します。1979年のSmallTalk-80の作業中に考案され、デスクトップアプリケーションやサーバサイドアプリケーションに活用され、JavaScriptにも適用されるようになりました。今では、MVCから派生してMVP(モデル・ビュー・プレゼンター)やMVVM(モデル・ビュー・ビューモデル)等のようなMV*系といったバリエーションが増えていきました。
MVCのそれぞれの役割を説明すると、以下のようになります。
モデル・・・アプリケーションのデータを管理
ビュー・・・モデルを視覚的に表現してその瞬間の状態を示すもの
コントローラ・・・モデルとビューの中間に位置し、ユーザがビューを操作した時にモデルを更新する
大まかな流れとしては、
- ビューを操作してコントローラがモデル更新を実行する
- モデル更新を監視していたビューが更新を受けて状態を反映する
になります。ちなみにこのとき注意するのは、
「モデルの更新処理はモデル側で行うものであり、ビューやコントローラはモデルのデータを参照できても直接更新をかけにいってはいけない 」
です。以前Javaを触ったときにカプセル化というのを習いましたが、モデルにはsetterがあり、ビュー・コントローラはgetterしかないというイメージでしょうか(違うかなぁ)。
ちなみに、はじめてMVCパターンの説明を受けた時、モデルというのはデータベースと同義と思っていました。
ただ、ここではフロントエンド領域でのMVCパターンであり、サーバからAPIで取得したデータをモデルに管理するという方が正しいのかもしれません。
関わった案件でいうと、ブラウザサイズだったり、サウンドを有効になっているかどうかといったものをモデルに管理させていましたが、これは作り物の要件によって変わってきます。モデルにどういうデータを管理させるかで、他のビューやコントローラの設計にも関わってくるように捉えています。
デモ
堅苦しい内容になってしまったので、実際に作ってみたいと思います。
シンプルではありますが、ToDoアプリです。
使用したものはjQueryと見た目の調整にMaterial Design Liteを使用しました。
https://qeita.github.io/js/mvc/jquery/todo/
Github Rep:
https://github.com/qeita/js/tree/master/mvc/jquery/todo
jQueryで作るにあたって
本サンプルでは、大きくModel・View・Controllerの3つのオブジェクトを用意しました。
ViewとControllerはnewしてインスタンス化しています。
最後にそれらをラップしたMain関数を実行させています。
https://github.com/qeita/js/blob/master/mvc/jquery/todo/assets/js/app.js
const Model = { ... }; const View = function(o){ this.o = o; this.init(); }; ~~~ const Controller = function(){ this.init(); }; ~~~ function Main(){ new Controller(); new View(document.querySelector('.js-list__box')); ... } Main();
まず、ToDoアプリを作るにあたりモデルにどのようなデータを管理するかを考えてみました。
サーバからAPIでデータベースのタスク情報を取得する想定で、itemData.json
をAjaxで取得するとします。
なので、取得したタスク情報を管理することにしました。todos
プロパティを作りここはシンプルに配列形式で進めます。
const Model = { todos: [], ... }
次に、コントローラ側にビューの要素をクリックした際、Model.seeTask(_n)
のように、モデル側のメソッドを叩くようにしています(この場合、モデルの値を直接更新かけずメソッドを参照しているだけ)。
メソッドを受けて、モデル側でデータの更新をしてから変更したということを通知する必要があります。ここは、jQueryのtriggerメソッドを使ってカスタムイベントを発火することにしました(triggerメソッドは自分で作ったカスタムイベントを発火させることができます)。
カスタムメソッドとは、クリックやマウスホバーのような組み込み定義されているイベントに対し、独自で定義したイベントです。ToDoアプリだと、タスクの読み込み・タスクの詳細表示・タスク追加・タスク削除を以下のようにモデル側に定義しました(今回は余分にモーダルの開閉イベントも定義しています)。
/** * イベント */ events: { LOAD_TASK: 'LOAD_TASK', ADD_TASK: 'ADD_TASK', SEE_TASK: 'SEE_TASK', OPEN_MODAL: 'OPEN_MODAL', CLOSE_MODAL: 'CLOSE_MODAL', REMOVE_TASK: 'REMOVE_TASK', },
最後に、ビューはどのようにしてモデル更新を受け取るのか。これは、Model変数を$(...)
でラップしてjQueryオブジェクトとして扱います。それをonメソッドでカスタムイベントを受け取り、以降ビュー内のメソッドで状態を更新するようにしています。
$(Model).on(Model.events.LOAD_TASK, function(ev){ me.render(me.o); });
振り返り
デジタルハリウッドにいた時は、jQueryを多用して見た目の処理にとらわれていたため全体の設計が意識できていなかったのですが、こうしてモデル・ビュー・コントローラを分けることで、UIにフォーカスした記述より行数的には膨れ上がってはいるものの、見通しとメンテナンスに良いコード作りになります。
今回はシンプルに1ファイルで完結しましたが、制作規模が大きくなったり多機能に及ぶ場合はモジュール毎にファイルを分けて管理しrequireやimportで結合する形になります。ビューも幾つかのUIがあるならそれに応じて分けて管理すべきところですし、モデルも今回はオブジェクトで作りましたが、複数のUIで参照されるようになるならシングルトンで管理し複数インスタンスを作らないようにしたりという方法もあります。
こういう手法を取り入れることで、大規模なサービスやサイト制作でも分業体制で進めやすくなります。逆に、ランディングページのような小規模で機能もそこまでないようなら使わなくても良いところで。
個人的には、コントローラ内のビュー操作がビューと分けられているのが気になり、やるならビュー内で操作も含めて管理してしまいたいという気持ちでいます。