今回は、実際に作る際の理論や設計方針を説明します。
コンポーネント指向
テンプレートエンジンやCSSメタ言語を活用
生のHTMLやCSSではコンポーネント指向は難しいので、テンプレートエンジンやCSSメタ言語を使いましょう。個人的なおすすめはPugとSass。
- どちらも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 件のコメント:
コメントを投稿