vue组件间通信

在Vue中,组件间通信是开发复杂应用的核心问题。根据组件关系(父子、兄弟、跨级、全局)的不同,通信方式也有所区别。以下是Vue组件间通信的8种常用方式。

一、父子组件通信

  1. Props(父->子)
  • 父组件通过props向子组件传递数据。
html 复制代码
<!-- 父组件 -->
<Child :title="parentTitle" />

<!-- 子组件 -->
<script>
export default {
  props: ['title'] // 接收数据
}
</script>

注意:Props是单向数据流,子组件不能直接修改。

  1. $emit/v-on(子->父)
  • 子组件通过$emit触发事件,父组件通过v-on监听。
html 复制代码
<!-- 父组件 -->
<Child @update="handleUpdate" />

<!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>

二、兄弟组件通信

  1. 共同父组件(Event Bus 模式)
  • 通过共同的父组件作为中介:
javascript 复制代码
// 父组件
<ChildA @event="handleEvent" />
<ChildB :data="sharedData" />

// ChildA 触发事件
this.$emit('event', data)

// 父组件讲数据传递给 ChildB 
  1. Event Bus(全局事件总线)
    Event Bus是一种发布-订阅模式的通信方式,适用于任意组件间通信(父子、兄弟、跨级),无需依赖共同的父组件或Vuex/Pinia。
  • 创建Event Bus
    首先,创建一个全局的Vue实例作为事件中心(通常在bus.js文件中定义):
javascript 复制代码
// src/utils/bus.js
import Vue from 'vue';
export const bus = new Vue(); // 导出一个Vue实例
  • 发送事件( e m i t )在需要发送数据的组件中,使用 ' b u s . emit) 在需要发送数据的组件中,使用`bus. emit)在需要发送数据的组件中,使用'bus.emit('事件名',数据)`:
javascript 复制代码
<!-- ComponentA.vue -->
<script>
import { bus } from '@utils/bus';
export default {
	methods: {
		sendMessage() {
			bus.$emit('message', 'Hello from ComponentA!');
		}
	}
}
</script>

<template>
	<button @click="sendMessage">发送消息</button>
</template>
  • 监听事件( o n )在需要接收数据的组件中,使用 ' b u s . on) 在需要接收数据的组件中,使用`bus. on)在需要接收数据的组件中,使用'bus.on('事件名', callback)`监听事件:
javascript 复制代码
<!-- ComponentB.vue -->
<script>
import { bus } from '@/utils/bus';
export default {
	created() {
		bus.$on('message', (data) => {
			console.log('收到消息:', data); // "Hello from ComponentA!"
		})
	},
	beforeDestroy() {
		// 组件销毁时,移除监听,避免内存泄漏
		bus.$off('message');
	}
}
</script>
  • 移除监听($off)
    为了避免内存泄漏,需要在组件销毁时移除监听:
javascript 复制代码
beforeDestory() {
	bus.$off('message'); // 移除单个事件监听
	// bus.$off(); // 移除所有监听(慎用)
}
  • 完整示例
    场景:
    ComponentA 发送消息
    ComponentB 和 ComponentC 接收消息
    (1)bus.js
javascript 复制代码
import Vue from 'vue';
export const bus = new Vue();

(2)ComponentA.vue(发送方)

javascript 复制代码
<script>
import { bus } from '@/utils/bus';

export default {
  methods: {
    sendData() {
      bus.$emit('update', { text: 'Hello Event Bus!' });
    }
  }
}
</script>

<template>
  <button @click="sendData">发送数据</button>
</template>

(3)ComponentB.vue(接收方1)

javascript 复制代码
<script>
import { bus } from '@/utils/bus';

export default {
  data() {
    return {
      receivedData: null
    };
  },
  created() {
    bus.$on('update', (data) => {
      this.receivedData = data.text;
    });
  },
  beforeDestroy() {
    bus.$off('update');
  }
}
</script>

<template>
  <div>收到数据: {{ receivedData }}</div>
</template>

(4)ComponentC.vue(接收方2)

javascript 复制代码
<script>
import { bus } from '@/utils/bus';

export default {
  created() {
    bus.$on('update', (data) => {
      alert(`ComponentC 收到: ${data.text}`);
    });
  },
  beforeDestroy() {
    bus.$off('update');
  }
}
</script>
  • 适用场景

    ✅ 任意组件间通信(父子、兄弟、跨级)

    ✅ 简单项目,不想引入 Vuex/Pinia

    ❌ 大型项目(建议用 Vuex/Pinia,Event Bus 难以维护)

  • 注意事项

  1. 内存泄漏:必须用 beforeDestroy 或 onUnmounted(Vue 3)移除监听。
  2. 调试困难:事件全局触发,难以追踪来源。
  3. 替代方案:
    Vue 2:Vue.observable(轻量状态管理)
    Vue 3:mitt(更小更快的 Event Bus 库)

三、跨级组件通信

  1. Provide/Inject
  • 祖先组件通过provide提供数据,后代组件通过inject注入:
javascript 复制代码
// 祖先组件
export default {
	provide() {
		return { theme: 'dark' };
	}
}

// 后代组件
export default {
	inject: ['theme'] // 直接适用this.theme
}

适用场景:深层嵌套组件(如 UI 库的主题配置)。

  1. attrs/listeners
    在Vue中,$attrs$listeners用于处理跨级组件通信透传属性和事件,特别适用于封装高阶组件(如自定义表单控件、UI组件库)。以下是详细示例和解析:
  • $attrs$listeners的作用
    $attrs:包含父组件传递的、未被props接收的非class/style属性,vue3中需v-bind="$attrs"
    $listeners:包含父组件传递的所有事件监听器(Vue2特有)

  • Vue2示例:透传属性和事件
    场景

    父组件-->中间组件-->子组件,透传placeholder属性和focus事件。

    (1) 父组件 (Parent.vue)

html 复制代码
<template>
	<MiddleComponent placeholder="请输入用户名" @focus="handleFocus" />
</template>

<script>
export default {
	methods: {
		handleFocus() {
			console.log("输入框获取焦点");
		}
	}
}
</script>

(2) 中间组件 (MiddleComponent.vue)

html 复制代码
<template>
	<!-- 透传所有属性和事件到子组件 -->
	<ChildComponet v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default {
	// 不声明props,让$atts接收所有属性
}
</script>

(3) 子组件 (ChildComponent.vue)

html 复制代码
<template>
	<input :placeholder="$attrs.placeholder"  <!-- 直接使用 $attrs -->
	@focus="$listeners.focus"  <!-- 触发父组件事件 -->
</template>

关键点:

  • 中间组件不声明props,让$attrs自动接收所有未声明的属性。

  • v-bind="$attrs"v-on="$listeners"透传到子组件。

  • Vue3示例($attrs包含事件)
    Vue3删除了$listeners,所有事件也通过$attrs传递。
    (1) 父组件 (Parent.vue)
html 复制代码
<template>
  <MiddleComponent
    placeholder="请输入密码"
    @focus="handleFocus"
  />
</template>

(2) 中间组件 (MiddleComponent.vue)

html 复制代码
<template>
  <ChildComponent v-bind="$attrs" />
</template>

(3) 子组件 (ChildComponent.vue)

html 复制代码
<template>
  <input
    :placeholder="$attrs.placeholder"
    @focus="$attrs.onFocus"  <!-- Vue 3 事件名变为 onXxx -->
  />
</template>

Vue3变化:

  1. 事件监听器在$attrs中以onXxx形式存在(如@focus->onFocus)。
  2. 不再需要 v-on="$listeners"
  • 高级用法:选择性透传
    如果中间组件需要拦截部分属性/事件,可以手动筛选:
    中间组件 (MiddleComponent.vue)
html 复制代码
<template>
	<ChildComponent 
	:placeholder="$attrs.placeholder" 
	@custom-event="handleCustomEvent"
	<!-- 只透传特定事件 -->
	v-on="filteredListeners"
	/>
</template>
<script>
export default {
	computed: {
		filteredListeners() {
			const { focus, ...rest } = this.$listeners; // 过滤掉 focus 事件
			return rest;
		}
	},
	methods: {
		handleCustomEvent() {
			console.log("拦截自定义事件");
		}
	}
}
</script>
  • 常见问题
    Q1: 为什么 $attrs 不包含 classstyle
  • Vue 默认将 classstyle 绑定到组件的根元素,如需透传需手动处理:
html 复制代码
<ChildCompoent :class="$attrs.class" />

Q2: 如何避免属性重复绑定?

  • 如果子组件的根元素已经绑定了$attrs,可以用inheritAttrs: false禁止默认行为:
javascript 复制代码
export default {
	inheritAttrs: false // 阻止自动绑定到根元素
}
  • 适用场景
  1. 封装表单控制(如自定义input): $attrs+$listeners
  2. 高阶组件(HOC): 透传所有未处理属性/事件
  3. UI组件库开发:避免属性丢失,保持灵活性
    总结:
  4. Vue 2:attrs(属性) + listeners(事件)分别透传。
  5. Vue 3:$attrs 包含属性和事件(事件名变为 onXxx)。
  6. 最佳实践:
    中间组件用 v-bind="attrs" 透传属性。 用 inheritAttrs: false 避免重复绑定。 需要拦截时手动筛选 attrs 或 $listeners。

四、全局状态管理

  1. Vuex(官方状态管理)
  • 集中式存储管理所有组件的状态:
javascript 复制代码
// store.js
export default new Vuex.Store({
  state: { count: 0 },
  mutations: {
    increment(state) { state.count++; }
  }
});

// 组件中使用
this.$store.commit('increment');
console.log(this.$store.state.count);

适用场景:中大型应用,需要共享状态的复杂场景。

  1. Pinia(Vue 3 推荐)
  • Vuex的替代方案,更简洁的API:
javascript 复制代码
// stores/counter.js
export const useCounterStore = defineStore('counter', {
	state: () => ({count: 0}),
	actions: {
		increment() { this.count++; }
	}
});

// 组件中使用
const counter = useCounterStore();
counter.increment();

五、其他方式

  • $refs: 直接访问子组件实例(禁耦合,慎用)。
  • LocalStorage/SesstionStorage: 持久化存储(非响应式,需手动监听)。
  • Vue3的setup+ defineExpose:暴露子组件方法。

选择通信方式的准则

  1. 父子组件:优先用 props + $emit。
  2. 兄弟组件:用共同的父组件或 Event Bus。
  3. 跨级组件:用 provide/inject 或 Vuex/Pinia。
  4. 全局状态:Vuex(Vue 2)或 Pinia(Vue 3)。
相关推荐
低代码布道师31 分钟前
第二部分:网页的妆容 —— CSS(下)
前端·css
一纸忘忧38 分钟前
成立一周年!开源的本土化中文文档知识库
前端·javascript·github
涵信1 小时前
第九节:性能优化高频题-首屏加载优化策略
前端·vue.js·性能优化
前端小巷子1 小时前
CSS单位完全指南
前端·css
SunTecTec2 小时前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
软件技术NINI2 小时前
html css js网页制作成品——HTML+CSS甜品店网页设计(4页)附源码
javascript·css·html
涵信2 小时前
第十一节:性能优化高频题-响应式数据深度监听问题
javascript·vue.js·性能优化
codingandsleeping3 小时前
Express入门
javascript·后端·node.js
Vaclee3 小时前
JavaScript-基础语法
开发语言·javascript·ecmascript
拉不动的猪3 小时前
前端常见数组分析
前端·javascript·面试