
MVVM(Model–View–ViewModel)パターンは、MVPの欠点(ViewとPresenterの結合の強さ)を解消して、双方向データバインディングによって、UIとデータを自動同期させることができるパターンです。
2005年頃に、MicrosoftがWPF(Windows Presentation Foundation)で提唱されました。
現在は React / Vue / Angular / Svelte などの基礎概念にもなっていますね。
構造
View(UI)
HTML, DOM
↓↑ 双方向データバインディング
ViewModel(UIロジック)
↓
Model(データ)
各役割の説明
Model(モデル)
・データやビジネスロジック。
・APIやDBとの通信を担当。
View(ビュー)
・UI要素(HTML, DOM)。
・自身ではロジックを持たず、ViewModelとデータを自動同期。
ViewModel(ビューモデル)
・Viewに表示するための中間ロジック。
・Viewの状態(文字、ボタン状態など)を保持。
・Modelのデータを変換・管理。
・双方向データバインディングを担う。
サンプルコード
<div id="app">
<h1>{{ count }}</h1>
<button data-action="increment">+</button>
<button data-action="decrement">−</button>
</div>
<script>
class Reactive {
constructor(data) {
this.listeners = [];
this.data = new Proxy(data, {
set: (obj, key, value) => {
obj[key] = value;
this.notify();
return true;
}
});
}
bind(callback) {
this.listeners.push(callback);
callback(this.data);
}
notify() {
this.listeners.forEach(cb => cb(this.data));
}
}
// Model
class CounterModel {
constructor() {
this.count = 0;
}
increment() { this.count++; }
decrement() { this.count--; }
}
// ViewModel
class CounterViewModel {
constructor(model) {
this.state = new Reactive({ count: model.count });
this.model = model;
}
increment() {
this.model.increment();
this.state.data.count = this.model.count;
}
decrement() {
this.model.decrement();
this.state.data.count = this.model.count;
}
}
// View
class CounterView {
constructor(vm) {
this.vm = vm;
this.el = document.getElementById("app");
this.bindEvents();
this.vm.state.bind(data => this.render(data));
}
bindEvents() {
this.el.addEventListener("click", e => {
const action = e.target.dataset.action;
if (action && this.vm[action]) this.vm[action]();
});
}
render(data) {
this.el.querySelector("h1").textContent = data.count;
}
}
// 起動
const model = new CounterModel();
const vm = new CounterViewModel(model);
new CounterView(vm);
</script>
ポイント解説
・View と ViewModel が データバインディング によって自動同期。
・イベントハンドラを ViewModel に集約。
・DOM操作(document.getElementByIdなど)をほぼ書かなくてよい。
特徴まとめ
| 要素 |
役割 |
特徴 |
| Model |
データ・ロジック |
ViewModelにのみ依存 |
| View |
UI表示 |
データの変更を自動反映 |
| ViewModel |
中間ロジック |
双方向データバインディング担当 |
メリット
・Viewとロジックの分離が徹底。
・テストが容易(Viewを操作せずに状態確認できる)。
・コードの再利用性が高い。
・双方向バインディングでUI更新が簡潔。
デメリット
・双方向データバインディングの仕組みが複雑になりやすい。
・大規模アプリでは依存関係が見えづらくなる。
・デバッグが難しくなることもある。
使われている場面
| 技術 |
MVVM的構造 |
| Vue.js |
`data()` ↔ テンプレートの自動バインディング |
| React(Hooks) |
状態とViewの一方向同期(MVVMの変形) |
| Angular |
`ngModel` などで双方向データバインディング |
| Knockout.js |
MVVMを直接実装した初期ライブラリ |
| SwiftUI / Jetpack Compose |
状態駆動UI(MVVM思想) |
あとがき
JS系のためのデザインパターンと言っても過言ではない、MVVMパターンです。
DOM操作を円滑に行うために、MVPを改良したパターンですね。
構造が少し複雑になるのが気になりますが、自分なりに、使いやすい構造ルールを作ると、便利に活用できそうです。
0 件のコメント:
コメントを投稿