2017年9月10日日曜日

あえてjQueryで挑むコンポーネント指向・MVVM(理論編)

前回は今時jQueryが必要な理由の背景と心構えを説明しました。

今回は、実際に作る際の理論や設計方針を説明します。


コンポーネント指向

テンプレートエンジンやCSSメタ言語を活用

生のHTMLやCSSではコンポーネント指向は難しいので、テンプレートエンジンやCSSメタ言語を使いましょう。

個人的なおすすめはPugSass
  • どちらもPythonのようにインデントベースでmixinも使えるので、見た目の統一感がある
  • どちらもnpmでインストールできるので、コマンド一発でビルドできる
  • どちらもインデントされて見やすいprettyモード、不要なスペースを除いたuglyモードの両方で出力できるので、開発時とリリース時で出力切り替えが簡単
見た目もビルドも出力も統一されているから便利でしょ?というわけです。

コンポーネントごとにファイルを分離

次はコンポーネントの分離作業。

コンポーネントごとに別ファイルに分離して、Pugならinclude、Sassなら@importで取り込みましょう。

Pugの場合はファイルに直接要素を書く(includeしたら問答無用でコンポーネントが出力される)のではなく、mixin内で定義→必要なところで呼び出したほうがいいです。
includeで直接引数を渡せないので。

WebpackでJavaScriptも分離

もちろんJavaScriptも機能ごとに別ファイルに分離しましょう。

細かいことを考えず、とりあえずWebpack使っとけ、な。

MVVM

続いてModel-View-ViewModelの実現方法について。

Model

ビジネスロジック部、つまり表示部分以外のコンポーネントの機能を作る部分です。

関数やクラスを提供するだけにとどめ、「import(require)されたら自動的に何かを実行する」ことは避けてください。

初期化等が必要な場合は、後述するViewModelから呼び出します。

View

画面表示部です。この場合はHTMLとCSSが該当しますので、JavaScriptでは特にやることはありません。

ViewModel

ViewとModel間の連携役です。

具体的にはViewのボタンがクリックされたことを検知してModelに処理を渡したり、逆にModelからデータを受け取ってViewに反映させたり…といった処理を行います。

JavaScriptのDOM操作(イベント処理やDOM構築等)が該当します。つまり、jQueryを使うのは原則としてViewModelの中だけです。

例外は、DOM操作以外のjQuery関数(jQuery.ajax()等)です。これはModel内で使っても問題ありません。

上で書いたModelの初期化関数は、jQueryのReadyハンドラ内で呼び出します。
jQueryのReadyハンドラは複数登録できますので、コンポーネントのViewModelごとにハンドラを登録して必要な処理(Modelの初期化やイベントハンドラの登録等)を行います。

注意事項

コンポーネント間の協調

これでコンポーネント指向&MVVMの基礎の設計はできました。
実際にシステムを作る上で問題になるのが、コンポーネント間の協調です。

具体的には、ビデオチャットのシステムで「デバイス情報View」と「ビデオ表示View」の2つのViewがあった場合に、「デバイス情報Viewに表示されているカメラを切り替えると、ビデオ表示Viewには新しく選択されたカメラの映像が表示される」といった処理です。

以前はこの処理がスマートに書けなかったために「デバイス情報のイベントハンドラ内でビデオ表示のModelを操作」のように複雑に入り組んだ処理を書かざるを得ず、若者のjQuery離れを引き起こしたのではないでしょうか。

今はEventEmitterという便利なものがあるので、こういったコンポーネント間の協調も非常にスマートに記述できます。

具体的には、
  • EventEmitterのインスタンスをViewModel間で共有
  • カメラが変更されたら「デバイス情報ViewModel」でemit()をコールし、新しいデバイスIDを渡す
  • emitされたイベントとデバイスIDをビデオViewModelで受け取り、必要な処理を行う(ViewModel内でビデオ表示部の変更、Modelを経由して相手に新しいストリームを送信等)
こんなかんじです。

EventEmitterのイベントハンドラもjQueryと同じく複数登録できるので、3つ以上のViewで協調動作したいときにも同じように使えます。

実行順序に依存させない

コンポーネント指向でもうひとつ注意するのが、コンポーネント間の実行順序を規定してはいけないという点です。

具体的には「コンポーネントAの初期化が完了しないとコンポーネントBを初期化してはいけない」のような依存関係を作らないようにしましょう、ということ。

とはいっても現実には絶対そういう場面が出てきます。その場合はPromiseを使いましょう。
  • Promiseインスタンスを作成
  • コンポーネントAから呼ばれる関数a()でresolve
  • コンポーネントBから呼ばれる関数b()では、resolveを待って実行
こうすれば、先にb()がコールされた場合でもエラーにはならず、a()がコールされるまで待機→その後にb()の本体が実行されるので、実行順序の問題も解決できます。

バインディング

最近のフレームワークの便利なところは、変数のバインディングが簡単に行えるところです。
配列に入っている値をそのままliタグで出力し、Model内の処理による要素数の増減にしたがって表示も変更、といったことが簡単にできます。

残念ながらjQueryではそこまで簡単にバインディングの処理はできませんが、
  • イベントハンドラでView→ViewModel→Modelへの通知
  • EventEmitterでModel→ViewModel→Viewへの通知
という必要最低限のことはできます。

次回はいよいよ実践編

小難しい話ばかり並べましたが、次回はようやく実践編として実際にどういうコードを書けばいいのかを紹介します。

ただし、来週は別の記事を書く予定なので実践編は再来週あたりで。

0 件のコメント:

コメントを投稿