vue常见问题

137 阅读3分钟
mvc和mvvm区别,适用场景

MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)是两种常见的软件架构模式,主要用于分离代码逻辑以提高代码的可维护性、可扩展性和复用性。

MVC(Model-View-Controller)

结构和特点:

  1. Modal(模型):

    1. 负责处理数据和业务逻辑。
    2. 通常与后端通信的部分(如获取、存储数据)。
  2. View(视图):

    1. 负责展示界面,面向用户的不分。
    2. 与用户直接交互。
  3. Controller(控制层):

    1. 负责接收用户输入,处理用户的操作,将操作分配给Model和View
    2. 是View和Modal 的桥梁

    优缺点

    1. 优点:

      1. 结构简单,容易理解和实现
      2. Modal和View相互独立,便于测试和维护

    缺点

    1. 当应用规模变大时,Controller可能会过于复杂(俗称“胖控制器”问题)
    2. View和Controller通常仍有一定程度的耦合

    适用场景

    1. 小型、简单的项目
    2. 需要快速开发、对复杂度不高的应用
    3. Web开发中常见的模式,如DjangoRuby on Rails

总结:就是 Controller负责将Model的数据用View显示出来,换句话说,在Controller里面把Model的数据赋值给View。

MVVM(Model-View-ViewModel)

结构和特点

  1. Model(模型)

    1. 和MVC的Model功能相似,负责业务逻辑和数据管理
  2. View(视图)

    1. 与用户直接交互,负责界面展示
  3. ViewModel(视图模型)

    1. 连接View和Model的桥梁
    2. 负责将Model的数据转化为UI所需的格式
    3. 通过双向绑定(或单向绑定)自动更新View和Model
    4. DOM的事件监听

优缺点

  1. 优点:

    1. View和Model解耦程度更高,双向绑定使得界面与数据的同步更简单
    2. 更适合组件化开发,尤其是动态交互较多的场景

缺点

  1. 学习曲线较高
  2. 数据绑定的实现可能增加调试负责度,尤其是复杂绑定的场景
  3. 在一些场景下可能导致性能问题

适用场景

  1. 前端开发中大量采用,如React+Redux(单向绑定)或Vue.js(双向绑定)
  2. 适合数据与界面频繁交互的应用(如实时更新的数据展示、复杂表单等)
  3. 客户端应用(如WPF、IOS/Android开发)经常采用

注意:Vue并没有完全遵循MVVM的思想。严格的MVVM要求View不能喝Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以说Vue没有完全遵循MVVM。


对比总结

特性MVCMVVM
数据绑定手动更行自动数据绑定(双向或单向)
复杂度简单,容易上手更复杂,学习成本较高
解耦程度View和Controller有耦合View和Model完全解耦
适用项目小型项目、简单应用中大型项目、动态交互复杂应用
为什么data是一个函数

在vue中,组件中的data是一个函数而不是对象,主要是为了确保每个组件实例有独立的数据作用域,避免共享数据时出现相互影响的问题。

  1. 确保组件的独立性

    1. 如果data是一个对象(而不是函数),那么所有的组件实例都会共享同一个data对象的引用
    2. 这种共享会导致多个组件实例的数据相互干扰。例如,如果一个组件修改了共享数据,其他组件实例中的数据也会被改变。
     // 问题示例
     data: {
       count: 0
     }
    

    如果data是对象形式,多个组件实例共享这一个count。当一个实例修改count时,所有实例都会受到影响。

  2. 使用函数返回对象,保证数据隔离

    1. data是一个函数时,每次创建组件实例时,都会调用这个函数,并返回一个新的数据对象,这确保了每个组件实例有独立的data数据。
     // 正确示例
     data() {
       return {
         count: 0
       }
     }
    

    每个实例的count都是独立的,不会互相干扰。

  3. 为什么根实例的data可以是对象

    对于根实例(通过 new Vue 创建的实例),data是直接使用的,因为根实例通常是单利,整个应用中只有一个实例。即使共享数据,也不会引起数据污染的问题。

     // 根实例
     new Vue({
       el: '#app',
       data: {
         count: 0
       }
     });
    
  4. 总结

    1. 组件data是函数:为每个组件实例返回一个独立的数据对象,避免数据共享引发的问题。
    2. 根实例data是对象:因为根实例通常是单例,不存在多个实例间的数据共享问题。
Vue组件通讯方式

在Vue中,组件之间的通信是前端开发中的核心部分,尤其在构建复杂的组件树时。根据组件之间的关系(父子、兄弟、跨层级等),vue提供了多种组件通信方式

  1. 父子组件通信

    • 父传子:通过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>
     ​
    
  2. 兄弟组件通信

    • 通过父组件中转

      兄弟组件通信可以通过父组件作为中转,兄弟组件通过 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"
       });
       ​
      
  3. 跨层级通信

    • provideinject

    在vue2.2+和vue3中,可以使用 provideinject 实现祖先组件与任意后代组件之间的通信。

     <!-- 祖先组件 -->
     <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"
       ​
      
  4. 跨页面通信

    • 通过URL或路由参数

      不同页面的组件可以通过Vue Router的参数(或者 queryparams )进行通信。

       // 通过路由传递参数
       this.$router.push({ name: 'pageB', query: { message: 'Hello' } });
       ​
       // 在另一个页面读取参数
       this.$route.query.message;
       ​
      
      • 使用浏览器API

        例如,通过 localStoragesessionStorageWebSocket 等持久化或实时通信的方式实现数据共享。

  5. 其他通信方式

    • 通过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>
       ​
      
  6. 总结

    通信方式使用场景示例
    props 和 $emit父子组件之间父传子,子传父
    EventBusvue2中兄弟组件通信简单应用,不推荐在vue3中使用
    provide 和 inject跨层级通信祖先与后代组件,灵活,但不适合频繁更新
    Vuex/Pinia全局状态管理,复杂场景跨组件、跨页面。强大,但引入依赖增加复杂度
    Vue Router跨页面通信路由参数
    ref父组件直接操作子组件调用组件方法,但父组件代码复杂化