MST
星途 面试题库

面试题:Vue组件通信之非父子组件通信

对于非父子关系的Vue组件,有哪些常见的通信方式?分别阐述每种方式的原理、适用场景,并举例说明如何在实际项目中使用事件总线和Vuex进行非父子组件通信,同时分析两者在大型项目中的优缺点。
31.0万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

常见通信方式

  1. 事件总线(Event Bus)
    • 原理:创建一个空的Vue实例作为事件总线,通过$on方法监听事件,$emit方法触发事件。非父子组件都可以访问这个事件总线实例,从而实现通信。
    • 适用场景:适用于组件间简单的、临时性的通信场景,特别是在小型项目或局部组件间通信。
    • 实际项目使用示例
      • 创建事件总线实例(假设在main.js中):
import Vue from 'vue';
// 创建事件总线实例
export const eventBus = new Vue(); 
  - 在发送数据的组件中:
<template>
  <button @click="sendData">发送数据</button>
</template>

<script>
import { eventBus } from '@/main';
export default {
  methods: {
    sendData() {
      const data = 'Hello, from non - parent component';
      eventBus.$emit('custom - event', data);
    }
  }
};
</script>
  - 在接收数据的组件中:
<template>
  <div>{{ receivedData }}</div>
</template>

<script>
import { eventBus } from '@/main';
export default {
  data() {
    return {
      receivedData: ''
    };
  },
  created() {
    eventBus.$on('custom - event', (data) => {
      this.receivedData = data;
    });
  }
};
</script>
- **在大型项目中的优缺点**:
  - **优点**:简单易用,不需要复杂的状态管理配置,对于局部组件通信可以快速实现。
  - **缺点**:随着项目规模扩大,事件总线可能会变得难以维护。事件的触发和监听可能分散在不同组件,追踪和调试困难,而且可能会出现命名冲突等问题。

2. Vuex - 原理:Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。组件通过commit提交mutation来修改状态,通过dispatch触发action来处理异步操作等。 - 适用场景:适用于大型单页应用,当多个组件需要共享状态,并且状态变化较为复杂时,Vuex可以更好地管理状态。 - 实际项目使用示例: - 首先配置Vuex(假设在store.js中):

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    sharedData: ''
  },
  mutations: {
    updateSharedData(state, data) {
      state.sharedData = data;
    }
  },
  actions: {
    updateSharedDataAsync({ commit }, data) {
      setTimeout(() => {
        commit('updateSharedData', data);
      }, 1000);
    }
  }
});

export default store;
  - 在发送数据的组件中:
<template>
  <button @click="sendData">发送数据</button>
</template>

<script>
export default {
  methods: {
    sendData() {
      const data = 'Hello, from non - parent component';
      // 提交mutation
      this.$store.commit('updateSharedData', data);
      // 或者触发action
      this.$store.dispatch('updateSharedDataAsync', data);
    }
  }
};
</script>
  - 在接收数据的组件中:
<template>
  <div>{{ sharedData }}</div>
</template>

<script>
export default {
  computed: {
    sharedData() {
      return this.$store.state.sharedData;
    }
  }
};
</script>
- **在大型项目中的优缺点**:
  - **优点**:状态管理集中化,便于维护和调试。所有状态变化都有迹可循,便于追踪和理解应用的数据流。同时,适合多人协作开发,能够更好地规范状态修改的方式。
  - **缺点**:引入了一定的学习成本,对于简单项目可能过于复杂。配置和使用相对繁琐,需要遵循严格的规则。而且如果滥用,可能会导致状态管理过度复杂,增加维护难度。

3. $attrs 和 $listeners - 原理$attrs包含了父作用域中不作为props被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何props时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过v - bind="$attrs"传入内部组件。$listeners包含了父作用域中的 (不含.native 修饰器的) v - on 事件监听器,可以通过v - on="$listeners"传入内部组件。这使得非父子组件通过中间组件间接传递数据和事件。 - 适用场景:适用于存在多层嵌套的组件结构,且需要跨级传递数据的场景,避免在中间组件逐一传递数据。 - 实际项目使用示例:假设存在A -> B -> C三层组件嵌套,A与C是非父子关系。 - 在A组件中:

<template>
  <BComponent :data - from - a="message" @custom - event - from - a="handleEvent"></BComponent>
</template>

<script>
import BComponent from './BComponent.vue';
export default {
  components: {
    BComponent
  },
  data() {
    return {
      message: 'Data from A'
    };
  },
  methods: {
    handleEvent() {
      console.log('Event received from C through B');
    }
  }
};
</script>
  - 在B组件中:
<template>
  <CComponent v - bind="$attrs" v - on="$listeners"></CComponent>
</template>

<script>
import CComponent from './CComponent.vue';
export default {
  components: {
    CComponent
  }
};
</script>
  - 在C组件中:
<template>
  <div>
    <p>{{ data - from - a }}</p>
    <button @click="$emit('custom - event - from - a')">触发事件到A</button>
  </div>
</template>

<script>
export default {
  inheritAttrs: false
};
</script>
- **在大型项目中的优缺点**:
  - **优点**:不需要额外的状态管理库,在多层嵌套组件间传递数据较为方便,减少了中间组件逐一传递数据的繁琐。
  - **缺点**:数据传递不够直观,容易在嵌套层级较深时造成数据流向混乱,增加调试难度。而且这种方式主要用于传递数据和事件,对于复杂的状态管理能力有限。

4. Vue Router - 原理:通过路由参数传递数据。当路由切换时,可以在路由配置中设置参数,不同组件根据当前路由参数获取数据,从而实现非父子组件间通信。 - 适用场景:适用于不同页面组件间的通信,尤其是在页面跳转过程中需要传递数据的场景。 - 实际项目使用示例: - 在路由配置(假设在router.js中):

import Vue from 'vue';
import Router from 'vue-router';
import ComponentA from './components/ComponentA.vue';
import ComponentB from './components/ComponentB.vue';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/component - a',
      name: 'ComponentA',
      component: ComponentA
    },
    {
      path: '/component - b/:data',
      name: 'ComponentB',
      component: ComponentB
    }
  ]
});
  - 在ComponentA组件中:
<template>
  <button @click="goToComponentB">跳转到ComponentB并传递数据</button>
</template>

<script>
export default {
  methods: {
    goToComponentB() {
      const data = 'Data from ComponentA';
      this.$router.push({ name: 'ComponentB', params: { data } });
    }
  }
};
</script>
  - 在ComponentB组件中:
<template>
  <div>{{ dataFromA }}</div>
</template>

<script>
export default {
  data() {
    return {
      dataFromA: ''
    };
  },
  created() {
    this.dataFromA = this.$route.params.data;
  }
};
</script>
- **在大型项目中的优缺点**:
  - **优点**:适用于页面级别的数据传递,结合路由功能,在页面跳转时传递数据很方便,符合页面间导航的业务逻辑。
  - **缺点**:只适用于路由切换场景下的数据传递,局限性较大。而且如果参数过多或复杂,路由配置和组件获取参数的逻辑会变得复杂,不利于维护。