从计数器Demo看MVC与MVVM:用Vue和React代码理解设计模式差异
每次面试被问到"MVC和MVVM有什么区别"时,你是不是也只会背"一个带Controller一个带ViewModel"?作为经历过无数场技术面试的老前端,我发现90%的候选人都在重复教科书定义,却说不清这些模式在实际项目中的真实表现。今天我们就用最直观的方式——可运行的代码对比,来破解这个经典面试题。
1. 环境准备:创建对比实验场
在开始编码前,我们先搭建一个公平的对比环境。假设要实现一个经典计数器功能:点击按钮时数字增加,分别用三种方式实现:
- 纯JavaScript(MVC模式)
- Vue 3(MVVM模式)
- React with Hooks(类MVVM模式)
提示:所有示例都使用现代ES6+语法,建议在CodePen或本地开发环境实时运行代码
先准备基础HTML结构:
html复制<!-- 公用HTML结构 -->
<div class="counter-container">
<span class="counter-value">0</span>
<button class="increment-btn">+1</button>
</div>
2. 原生JavaScript实现MVC模式
让我们先用最原始的方式实现MVC架构。注意观察各层之间的调用关系:
javascript复制// Model层 - 处理数据逻辑
class CounterModel {
constructor() {
this.value = 0;
}
increment() {
this.value += 1;
return this.value;
}
}
// View层 - 处理UI展示
class CounterView {
constructor() {
this.valueEl = document.querySelector('.counter-value');
this.buttonEl = document.querySelector('.increment-btn');
}
updateValue(newValue) {
this.valueEl.textContent = newValue;
}
bindIncrement(handler) {
this.buttonEl.addEventListener('click', handler);
}
}
// Controller层 - 协调Model和View
class CounterController {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.bindIncrement(this.handleIncrement.bind(this));
}
handleIncrement() {
const newValue = this.model.increment();
this.view.updateValue(newValue);
}
}
// 初始化应用
const app = new CounterController(new CounterModel(), new CounterView());
关键观察点:
- 数据流向:View → Controller → Model → View 的显式调用链
- DOM操作:需要手动查询和更新DOM元素
- 代码量:约40行代码,分层明确但略显冗长
3. Vue 3实现MVVM模式
现在用Vue 3的Composition API实现相同功能,注意ViewModel如何自动处理数据绑定:
html复制<div id="app" class="counter-container">
<span class="counter-value">{{ count }}</span>
<button class="increment-btn" @click="increment">+1</button>
</div>
<script>
const { ref } = Vue;
const app = Vue.createApp({
setup() {
const count = ref(0);
const increment = () => {
count.value += 1;
};
return { count, increment };
}
});
app.mount('#app');
</script>
对比发现:
- 数据绑定:通过
{{ count }}自动同步数据到视图 - 事件绑定:
@click直接关联方法,无需手动addEventListener - 代码量:约15行,逻辑更紧凑
- ViewModel:Vue内部创建的响应式系统就是ViewModel
4. React Hooks实现类MVVM模式
React虽然不完全符合经典MVVM,但Hooks提供了类似的开发体验:
jsx复制import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prev => prev + 1);
};
return (
<div className="counter-container">
<span className="counter-value">{count}</span>
<button className="increment-btn" onClick={increment}>
+1
</button>
</div>
);
}
React的特点:
- 单向数据流:虽不是双向绑定,但useState+JSX实现了类似效果
- 虚拟DOM:自动处理DOM更新,开发者只需关心状态
- 函数式思维:UI是状态的函数,状态变更触发UI更新
5. 模式对比与选型建议
通过三种实现方式的对比,我们可以总结出关键差异:
| 特性 | MVC | MVVM |
|---|---|---|
| 数据流向 | 单向显式调用 | 双向自动绑定 |
| DOM操作 | 手动更新 | 框架自动处理 |
| 代码量 | 较多 | 较少 |
| 学习曲线 | 平缓 | 较陡峭 |
| 适用场景 | 传统多页面应用 | 现代SPA应用 |
| 典型框架 | Backbone.js | Vue, Angular |
实际项目选型建议:
-
选择MVC:
- 需要精细控制DOM操作的项目
- 遗留系统维护或渐进式增强场景
- 团队对传统模式更熟悉
-
选择MVVM:
- 数据驱动的复杂单页应用
- 需要快速迭代的现代Web应用
- 追求开发效率和代码简洁性
6. 常见误区与面试要点
在技术面试中,关于MVC/MVVM常有几个误区需要澄清:
-
React是MVVM吗?
- 不完全算。React更接近"View = f(State)"的函数式理念
- 但Hooks的出现让开发体验接近MVVM
-
双向绑定是必须的吗?
- MVVM不强制要求双向绑定
- Vue通过v-model提供便捷的双向绑定,但核心仍是单向数据流
-
哪种模式性能更好?
- 没有绝对优劣,取决于实现方式
- MVC手动操作DOM在简单场景可能更快
- MVVM的虚拟DOM在大规模更新时更高效
面试时可重点准备:
- 能现场手写两种模式的计数器Demo
- 说清楚数据流向的差异
- 结合实际项目经验谈选型考量