mvc和mvvm区别,适用场景
MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)是两种常见的软件架构模式,主要用于分离代码逻辑以提高代码的可维护性、可扩展性和复用性。
MVC(Model-View-Controller)
结构和特点:
-
Modal(模型):
- 负责处理数据和业务逻辑。
- 通常与后端通信的部分(如获取、存储数据)。
-
View(视图):
- 负责展示界面,面向用户的不分。
- 与用户直接交互。
-
Controller(控制层):
- 负责接收用户输入,处理用户的操作,将操作分配给Model和View
- 是View和Modal 的桥梁
优缺点
-
优点:
- 结构简单,容易理解和实现
- Modal和View相互独立,便于测试和维护
缺点
- 当应用规模变大时,Controller可能会过于复杂(俗称“胖控制器”问题)
- View和Controller通常仍有一定程度的耦合
适用场景
- 小型、简单的项目
- 需要快速开发、对复杂度不高的应用
- Web开发中常见的模式,如
Django、Ruby on Rails
总结:就是 Controller负责将Model的数据用View显示出来,换句话说,在Controller里面把Model的数据赋值给View。
MVVM(Model-View-ViewModel)
结构和特点
-
Model(模型)
- 和MVC的Model功能相似,负责业务逻辑和数据管理
-
View(视图)
- 与用户直接交互,负责界面展示
-
ViewModel(视图模型)
- 连接View和Model的桥梁
- 负责将Model的数据转化为UI所需的格式
- 通过双向绑定(或单向绑定)自动更新View和Model
- DOM的事件监听
优缺点
-
优点:
- View和Model解耦程度更高,双向绑定使得界面与数据的同步更简单
- 更适合组件化开发,尤其是动态交互较多的场景
缺点
- 学习曲线较高
- 数据绑定的实现可能增加调试负责度,尤其是复杂绑定的场景
- 在一些场景下可能导致性能问题
适用场景
- 前端开发中大量采用,如
React+Redux(单向绑定)或Vue.js(双向绑定) - 适合数据与界面频繁交互的应用(如实时更新的数据展示、复杂表单等)
- 客户端应用(如WPF、IOS/Android开发)经常采用
注意:Vue并没有完全遵循MVVM的思想。严格的MVVM要求View不能喝Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以说Vue没有完全遵循MVVM。
对比总结
| 特性 | MVC | MVVM |
|---|---|---|
| 数据绑定 | 手动更行 | 自动数据绑定(双向或单向) |
| 复杂度 | 简单,容易上手 | 更复杂,学习成本较高 |
| 解耦程度 | View和Controller有耦合 | View和Model完全解耦 |
| 适用项目 | 小型项目、简单应用 | 中大型项目、动态交互复杂应用 |
为什么data是一个函数
在vue中,组件中的data是一个函数而不是对象,主要是为了确保每个组件实例有独立的数据作用域,避免共享数据时出现相互影响的问题。
-
确保组件的独立性
- 如果
data是一个对象(而不是函数),那么所有的组件实例都会共享同一个data对象的引用 - 这种共享会导致多个组件实例的数据相互干扰。例如,如果一个组件修改了共享数据,其他组件实例中的数据也会被改变。
// 问题示例 data: { count: 0 }如果
data是对象形式,多个组件实例共享这一个count。当一个实例修改count时,所有实例都会受到影响。 - 如果
-
使用函数返回对象,保证数据隔离
- 当
data是一个函数时,每次创建组件实例时,都会调用这个函数,并返回一个新的数据对象,这确保了每个组件实例有独立的data数据。
// 正确示例 data() { return { count: 0 } }每个实例的
count都是独立的,不会互相干扰。 - 当
-
为什么根实例的
data可以是对象对于根实例(通过
new Vue创建的实例),data是直接使用的,因为根实例通常是单利,整个应用中只有一个实例。即使共享数据,也不会引起数据污染的问题。// 根实例 new Vue({ el: '#app', data: { count: 0 } }); -
总结
- 组件data是函数:为每个组件实例返回一个独立的数据对象,避免数据共享引发的问题。
- 根实例data是对象:因为根实例通常是单例,不存在多个实例间的数据共享问题。
Vue组件通讯方式
在Vue中,组件之间的通信是前端开发中的核心部分,尤其在构建复杂的组件树时。根据组件之间的关系(父子、兄弟、跨层级等),vue提供了多种组件通信方式
-
父子组件通信
-
父传子:通过
props父组件通过 props 将数据传递给子组件,子组件通过props 接收数据。
<!-- 父组件 --> <template> <child-component :message="parentMessage"></child-component> </template> <script> export default { data() { return { parentMessage: "Hello from Parent" }; } }; </script> <!-- 子组件 --> <template> <p>{{ message }}</p> </template> <script> export default { props: ['message'] }; </script> -
子传父:通过事件
$emit子组件可以使用
$emit触发事件,父组件监听该事件,并执行相应逻辑
<!-- 子组件 --> <template> <button @click="notifyParent">Click me</button> </template> <script> export default { methods: { notifyParent() { this.$emit('child-event', 'Hello from Child'); } } }; </script> <!-- 父组件 --> <template> <child-component @child-event="handleChildEvent"></child-component> </template> <script> export default { methods: { handleChildEvent(message) { console.log(message); // 输出 "Hello from Child" } } }; </script> -
-
兄弟组件通信
-
通过父组件中转
兄弟组件通信可以通过父组件作为中转,兄弟组件通过
props和$emit与父组件交互,父组件将数据传递给其他兄弟组件. -
通过事件总线(Event Bus)
在vue2中,可以使用一个空的Vue实例作为事件总线(Event Bus),在一个组件中触发事件,另一个组件监听该事件。
// 创建 Event Bus export const EventBus = new Vue(); // 组件 A EventBus.$emit('eventName', data); // 组件 B EventBus.$on('eventName', (data) => { console.log(data); }); -
在vue3 中使用 mitt 实现事件总线
vue3 不再推荐使用vue2 中的
EventBus,但可以使用轻量化的事件库如mitt来实现。//npm install mitt // eventBus.js import mitt from 'mitt'; export const EventBus = mitt(); // 兄弟组件 A import { EventBus } from './eventBus'; EventBus.emit('custom-event', 'Hello from A'); // 兄弟组件 B import { EventBus } from './eventBus'; EventBus.on('custom-event', (data) => { console.log(data); // "Hello from A" });
-
-
跨层级通信
provide和inject
在vue2.2+和vue3中,可以使用
provide和inject实现祖先组件与任意后代组件之间的通信。<!-- 祖先组件 --> <script> export default { provide() { return { sharedData: "Hello from Ancestor" }; } }; </script> <!-- 后代组件 --> <script> export default { inject: ['sharedData'], mounted() { console.log(this.sharedData); // 输出 "Hello from Ancestor" } }; </script> -
全局状态管理工具(Vuex/Pinia)
对于更复杂的应用,可以使用状态管理工具(如Vuex 或Pinia)来集中管理应用的状态,实现跨组件的高效通信。
//安装 Pinia //npm install pinia // 创建状态存储 // store.js import { defineStore } from 'pinia'; export const useMessageStore = defineStore('message', { state: () => ({ message: '' }), actions: { setMessage(msg) { this.message = msg; } } }); // 在兄弟组件中使用 // 兄弟组件 A import { useMessageStore } from './store'; const store = useMessageStore(); store.setMessage('Hello from A'); // 兄弟组件 B import { useMessageStore } from './store'; const store = useMessageStore(); console.log(store.message); // "Hello from A"
-
跨页面通信
-
通过URL或路由参数
不同页面的组件可以通过Vue Router的参数(或者
query或params)进行通信。// 通过路由传递参数 this.$router.push({ name: 'pageB', query: { message: 'Hello' } }); // 在另一个页面读取参数 this.$route.query.message; -
使用浏览器API
例如,通过
localStorage、sessionStorage、WebSocket等持久化或实时通信的方式实现数据共享。
-
-
-
其他通信方式
-
通过Vue 的
ref父组件通过 ref 直接访问子组件实例,从而调用子组件的方法或操作子组件的数据。
<!-- 父组件 --> <template> <child-component ref="child"></child-component> <button @click="callChildMethod">Call Child Method</button> </template> <script> export default { methods: { callChildMethod() { this.$refs.child.someMethod(); } } }; </script> <!-- 子组件 --> <script> export default { methods: { someMethod() { console.log("Method called from parent"); } } }; </script>
-
-
总结
通信方式 使用场景 示例 props 和 $emit 父子组件之间 父传子,子传父 EventBus vue2中兄弟组件通信 简单应用,不推荐在vue3中使用 provide 和 inject 跨层级通信 祖先与后代组件,灵活,但不适合频繁更新 Vuex/Pinia 全局状态管理,复杂场景 跨组件、跨页面。强大,但引入依赖增加复杂度 Vue Router 跨页面通信 路由参数 ref 父组件直接操作子组件 调用组件方法,但父组件代码复杂化