はじめに
Webアプリ開発では、SPAやSSRといったアプリの構成の選定と同時に、MVCやMVVMなどの設計パターンも検討する必要がある
本記事では、そもそもSPAではなぜMVVMが主流で、SSRではMVCが多いのか?を体系的に整理したい。
1. MVC と MVVM の違いをおさらい
項目 | MVC(Model-View-Controller) | MVVM(Model-View-ViewModel) |
---|---|---|
アーキ構成 | Model / View / Controller | Model / View / ViewModel |
UI更新方法 | Controller経由でイベント → Model変更 → View更新 | データバインディングによりModelとViewを自動同期 |
責務の分離 | 明確。UIロジックはControllerに集約。 | ViewModelがViewとModelの橋渡し。 |
主な採用例 | Ruby on Rails, Laravel, ASP.NET MVC | React + Hooks, Vue.js, Knockout.jsなど |
要点
- MVCは“制御”が中心。イベントを受けてViewを切り替える。
- MVVMは“状態”が中心。状態が変わればUIが勝手に変わる。
2. サンプルアプリで比較(よくあるTodo管理)
MVC的アプローチ(例:Express + EJS)
// controller.js
exports.addTask = (req, res) => {
const task = req.body.task;
TaskModel.add(task);
res.redirect('/');
};
<!-- view.ejs -->
<form action="/add" method="POST">
<input name="task" />
<button>追加</button>
</form>
<ul>
<% tasks.forEach(t => { %>
<li><%= t %></li>
<% }) %>
</ul>
MVVM的アプローチ(例:React)
function TodoApp() {
const [tasks, setTasks] = useState([]);
const [input, setInput] = useState('');
const addTask = () => {
setTasks([...tasks, input]);
setInput('');
};
return (
<>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={addTask}>追加</button>
<ul>{tasks.map(t => <li key={t}>{t}</li>)}</ul>
</>
);
}
違い
- MVCでは「入力」→「Controller経由でデータ更新」→「View再描画」
- MVVMでは「状態を変更」するだけでUIが自動反映(=双方向バインディング)
じゃあSSRはMVCが、SPAはMVVMが主流といわれるのはなぜかといわれると
-
SSRでは、サーバーがViewもControlも持つため「1リクエスト = 1画面 = 1制御」が自然
→ つまりMVCで制御の流れを分けやすいから -
SPAでは、ViewとModelをクライアント側で保持し、ユーザー操作が頻繁に起きる
→ リアクティブUI構成に適したMVVMのほうが保守・拡張に向いてるから
3. 最近の傾向と所感
- ひと昔前はMVCの流れでSSR/MPAが主流だった
- 2015年くらいからはMVVM + SPA(React/Vue)が主流だったが、SEO問題とかもあって最近は結局SSR回帰(Next.js, Nuxt)が主流な印象
- SSR + SPA のようなハイブリッド構成も一般化してる(例:Next.jsのApp Routerとか)
個人的には、MVC(SSR/MPA構成)が分かりやすくて安心感がある。
MVVMで状態管理を複雑にするより、リクエストごとにViewを切り替えるMVCの方が設計的に筋が通っていてやっぱりわかりやすいから好き。
SPAが流行ったのは確かにUIのレスポンス高速化、ユーザー体験向上とか背景があるけど、MVCをやってきた人にSPA+MVVMはとっつきにくいと思う(自分がそうだった)。
参考:Webの将来はサーバサイドレンダリング(SSR)に回帰していく
https://www.publickey1.jp/blog/23/webssrdenoisomorphic_javascriptuniversal_javascript.html