Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性

在前两篇文章中,我们学习了Props、ref、自定义事件和provide/inject等组件通信方式。今天我们将继续深入学习四种更高级的通信手段:全局事件总线消息订阅与发布插槽技术 以及组件实例的其他属性。掌握这些内容,你将能应对几乎所有组件通信场景。

一、全局事件总线(Global Event Bus)

1.1 什么是全局事件总线?

全局事件总线的核心思想是:给项目中所有的组件找一个共享的vc对象,所有的事件都绑定到这个共享对象上,所有组件都通过这个共享对象来传递数据。

共享对象必须具备两个特征

  1. 能够让所有的vc共享访问
  2. 拥有 $on$off$emit 等方法

1.2 实现原理

方式一:显式创建Vue组件实例
复制代码
// main.js
import Vue from 'vue';
import App from './App.vue';

// 创建一个VueComponent实例作为总线
const VueComponent = Vue.extend({});
const globalVc = new VueComponent();
Vue.prototype.$bus = globalVc;

new Vue({
    render: h => h(App)
}).$mount('#app');
方式二:在根实例beforeCreate中挂载(推荐)
复制代码
// main.js
import Vue from 'vue';
import App from './App.vue';

new Vue({
    beforeCreate() {
        // 将当前vm挂载到Vue原型上,所有组件都能访问
        Vue.prototype.$bus = this;
    },
    render: h => h(App)
}).$mount('#app');

推荐使用方式二,因为:

  • 代码更简洁
  • 直接利用已有的vm实例,无需额外创建
  • 避免了不必要的性能开销

1.3 事件通信模式

发送事件(触发方)

girl.vue​ - 发送数据:

复制代码
<template>
    <div class="girl">
        <h2>girl组件</h2>
        <h3>girlNum: {{ girlNum }}</h3>
        <button @click="sendGirlNum">点击传递数据给son</button>
    </div>
</template>

<script>
export default {
    name: 'girl',
    data() {
        return {
            girlNum: 777
        };
    },
    methods: {
        sendGirlNum() {
            // 通过$bus触发事件,传递数据
            this.$bus.$emit('getGirlNum', this.girlNum);
        }
    }
};
</script>

child.vue​ - 发送数据:

复制代码
<template>
    <div class="child">
        <h3>child组件</h3>
        <button @click="$bus.$emit('sendMsg', childMsg)">点击传递数据给son</button>
    </div>
</template>

<script>
export default {
    name: 'child',
    data() {
        return {
            childMsg: '我是child组件的数据'
        };
    }
};
</script>
接收事件(接收方)

son.vue​ - 接收数据:

复制代码
<template>
    <div class="son">
        <h2>son组件</h2>
        <h3>来自girl组件的girlNum: {{ girlNum }}</h3>
        <h3>来自child组件的消息: {{ childMsg }}</h3>
    </div>
</template>

<script>
export default {
    name: 'son',
    data() {
        return {
            girlNum: '',
            childMsg: ''
        };
    },
    mounted() {
        // 在son组件中绑定事件,接收数据
        this.$bus.$on('getGirlNum', (val) => {
            this.girlNum = val;
        });
        
        this.$bus.$on('sendMsg', (val) => {
            this.childMsg = val;
            console.log('接收child数据', val);
        });
    },
    beforeDestroy() {
        // 组件销毁前清理事件监听
        this.$bus.$off();
    }
};
</script>

1.4 核心原则

永远记住:A组件向B组件传数据,应该在B组件中绑定事件(接收),在A组件中触发事件(发送)。

角色 行为 API
发送方(A组件) 触发事件 this.$bus.$emit('eventName', data)
接收方(B组件) 绑定事件 this.$bus.$on('eventName', callback)
清理 解绑事件 this.$bus.$off()

1.5 注意事项

注意点 说明
事件清理 beforeDestroy 中务必解绑事件,防止内存泄漏
事件命名 避免事件名冲突,建议使用统一的命名规范
调试难度 事件总线使数据流向不清晰,大型项目建议使用Vuex

二、消息订阅与发布(PubSub)

2.1 什么是PubSub?

PubSub是一种跨框架通用的消息机制,通过发布/订阅模式实现组件间的通信。它不仅限于Vue,在任何前端框架甚至原生JS中都可以使用。

安装

复制代码
npm install pubsub-js

2.2 配置全局PubSub

复制代码
// main.js
import Vue from 'vue';
import App from './App.vue';
import pubsub from 'pubsub-js';

// 将pubsub挂载到Vue原型上
Vue.prototype.$pubsub = pubsub;

new Vue({
    render: h => h(App)
}).$mount('#app');

2.3 发布消息

girl.vue​ - 发布消息:

复制代码
<template>
    <div class="girl">
        <h2>girl组件</h2>
        <h3>girlNum: {{ girlNum }}</h3>
        <button @click="sendMsg">点击发布消息</button>
    </div>
</template>

<script>
export default {
    name: 'girl',
    data() {
        return {
            girlNum: 888
        };
    },
    methods: {
        sendMsg() {
            // 发布消息,传递数据
            this.$pubsub.publish('girlNum', this.girlNum);
        }
    }
};
</script>

2.4 订阅消息

son.vue​ - 订阅消息:

复制代码
<template>
    <div class="son">
        <h2>son组件</h2>
        <h3>来自girl组件的girlNum: {{ girlNum }}</h3>
    </div>
</template>

<script>
export default {
    name: 'son',
    data() {
        return {
            girlNum: '',
            pid: '' // 存储订阅ID,用于取消订阅
        };
    },
    mounted() {
        // 订阅消息
        this.pid = this.$pubsub.subscribe('girlNum', (name, val) => {
            this.girlNum = val;
            console.log('消息名:', name, '数据:', val);
        });
    },
    beforeDestroy() {
        // 组件销毁前,取消消息的订阅
        this.$pubsub.unsubscribe(this.pid);
    }
};
</script>

2.5 PubSub vs 事件总线

对比项 全局事件总线 PubSub
所属框架 Vue特有 跨框架通用
实现方式 基于Vue实例的$on/$emit 第三方库
安装 无需安装 需要安装pubsub-js
回调参数 直接接收数据 第一个参数为消息名,第二个为数据
清理方式 $off() unsubscribe()

三、插槽技术(Slots)

插槽是Vue中内容分发的重要机制,允许父组件向子组件传递模板内容。

3.1 默认插槽

**父组件(App.vue)**​ - 传递内容:

复制代码
<template>
    <div id="app">
        <Myltem title="名著">
            <div>
                <h4 v-for="item in books" :key="item">{{ item }}</h4>
            </div>
        </Myltem>
    </div>
</template>

<script>
import Myltem from './components/Myltem.vue';

export default {
    name: 'App',
    data() {
        return {
            books: ['红楼梦', '西游记', '水浒传', '三国演义']
        };
    },
    components: { Myltem }
};
</script>

**子组件(Myltem.vue)**​ - 接收内容:

复制代码
<template>
    <div class="my-item">
        <h3>{{ title }}</h3>
        <!-- 默认插槽,显示父组件传递的内容 -->
        <slot>默认内容(当父组件没有传递内容时显示)</slot>
    </div>
</template>

<script>
export default {
    name: 'Myltem',
    props: ['title']
};
</script>

3.2 具名插槽

当一个组件中有多个插槽时,需要使用具名插槽来区分。

**子组件(Myltem.vue)**​ - 定义具名插槽:

复制代码
<template>
    <div class="my-item">
        <h3>{{ title }}</h3>
        <slot name="books">默认书籍内容</slot>
        <hr>
        <slot name="more">更多内容</slot>
    </div>
</template>

**父组件(App.vue)**​ - 三种写法:

复制代码
<template>
    <div id="app">
        <Myltem title="名著">
            <!-- 写法一:使用slot属性(Vue2.6以前的方式) -->
            <a href="#" slot="more">查看更多</a>
            
            <!-- 写法二:使用template + #简写(推荐) -->
            <template #books>
                <h4 v-for="item in books" :key="item">{{ item }}</h4>
            </template>
            
            <!-- 写法三:使用template + v-slot -->
            <template v-slot:more>
                <a href="#">查看更多</a>
            </template>
        </Myltem>
    </div>
</template>

三种写法对比

写法 语法 适用版本 推荐程度
写法一 slot="name" Vue2.6+ 兼容旧版
写法二 #name Vue2.6+ ⭐推荐
写法三 v-slot:name Vue2.6+ 完整语法

3.3 作用域插槽

作用域插槽允许子组件向父组件传递数据,父组件可以根据这些数据来决定如何渲染插槽内容。

**子组件(Myltem.vue)**​ - 传递数据给父组件:

复制代码
<template>
    <div class="my-item">
        <h3>{{ title }}</h3>
        <!-- 通过插槽属性传递数据给父组件 -->
        <slot :books="books" :names="names" :movies="movies"></slot>
    </div>
</template>

<script>
export default {
    name: 'Myltem',
    data() {
        return {
            books: ['红楼梦', '西游记', '水浒传', '三国演义'],
            names: ['张三', '李四', '王五'],
            movies: ['流浪地球', '战狼', '长津湖']
        };
    }
};
</script>

**父组件(App.vue)**​ - 接收子组件数据:

复制代码
<template>
    <div id="app">
        <!-- 写法一:scope接收数据 -->
        <Myltem title="名著">
            <template scope="data">
                <h4 v-for="item in data.books" :key="item">{{ item }}</h4>
            </template>
        </Myltem>
        
        <!-- 写法二:解构赋值 -->
        <Myltem title="人名">
            <template scope="{ names }">
                <ul>
                    <li v-for="item in names" :key="item">{{ item }}</li>
                </ul>
            </template>
        </Myltem>
        
        <!-- 写法三:slot-scope(Vue2.6之前的写法) -->
        <Myltem title="电影">
            <template slot-scope="{ books }">
                <h4 v-for="(book, index) in books" :key="index">{{ book }}</h4>
            </template>
        </Myltem>
    </div>
</template>

作用域插槽的三种写法

写法 语法 说明
scope scope="data" Vue2.6+推荐
解构 scope="{ books }" 更简洁
slot-scope slot-scope="{ books }" 旧版写法,兼容

3.4 插槽总结

插槽类型 用途 子组件定义 父组件使用
默认插槽 单一内容分发 <slot> 直接写内容
具名插槽 多内容分发 <slot name="xxx"> #xxxv-slot:xxx
作用域插槽 子传数据给父组件渲染 <slot :data="data"> scopeslot-scope

四、组件实例的其他属性

4.1 $children - 访问子组件

$children 返回当前组件的所有直接子组件实例的数组。

复制代码
// App.vue
export default {
    methods: {
        getAllChild() {
            // 获取所有直接子组件
            console.log(this.$children);
            // 可以遍历访问每个子组件的数据和方法
            this.$children.forEach(child => {
                console.log(child.$options.name);
            });
        }
    }
};

⚠️ 注意事项

  • $children 不保证顺序
  • 依赖于子组件的挂载顺序
  • 不建议频繁使用,推荐使用 ref 精确访问

4.2 parent和root - 访问父组件和根实例

复制代码
// son.vue
export default {
    methods: {
        getParent() {
            // 获取直接父组件
            console.log(this.$parent);
            
            // 获取根实例(vm)
            console.log(this.$root);
            
            // 通过$parent可以访问父组件的数据和方法
            console.log(this.$parent.parentData);
            this.$parent.parentMethod();
        }
    }
};
属性 说明 返回值
$parent 当前组件的直接父组件 VueComponent实例
$root 根Vue实例(vm) Vue实例

4.3 attrs和listeners - 属性透传

当需要在中间组件透传属性和事件时,$attrs$listeners 非常有用。

**父组件(App.vue)**​ - 传递属性和事件:

复制代码
<template>
    <div id="app">
        <son :appNum="appNum" :appMsg="appMsg" @appFun="appFun" />
    </div>
</template>

<script>
export default {
    data() {
        return {
            appNum: 123,
            appMsg: '来自App的消息'
        };
    },
    methods: {
        appFun() {
            console.log('触发了appFun事件');
        }
    }
};
</script>

**中间组件(son.vue)**​ - 透传属性和事件:

复制代码
<template>
    <div class="son">
        <h2>son组件</h2>
        <!-- 通过v-bind="$attrs"和v-on="$listeners"透传 -->
        <child v-bind="$attrs" v-on="$listeners" />
    </div>
</template>

<script>
import child from './child.vue';

export default {
    name: 'son',
    components: { child }
};
</script>

**最终子组件(child.vue)**​ - 使用透传的属性:

复制代码
<template>
    <div class="child">
        <h3>child组件</h3>
        <!-- 使用从son透传过来的属性 -->
        <h4>appNum: {{ $attrs.appNum }}</h4>
        <h4>appMsg: {{ $attrs.appMsg }}</h4>
        <!-- 触发透传过来的事件 -->
        <button @click="$emit('appFun')">触发事件</button>
    </div>
</template>

<script>
export default {
    name: 'child'
};
</script>

attrs和listeners 的作用

属性 说明 用途
$attrs 父组件传递的非prop属性 透传属性到更深层组件
$listeners 父组件传递的自定义事件 透传事件到更深层组件

五、核心概念总结

5.1 通信方式一览

通信方式 方向 适用场景 复杂度
全局事件总线 任意组件 中小型项目,任意组件通信 ⭐⭐
PubSub 任意组件 跨框架项目,消息机制 ⭐⭐
默认插槽 父→子 内容分发
具名插槽 父→子 多区域内容分发
作用域插槽 子→父 子组件控制渲染逻辑 ⭐⭐
parent/children 父子 快速访问父子组件
attrs/listeners 跨级 属性/事件透传 ⭐⭐⭐

5.2 最佳实践建议

场景 推荐方案
父子组件通信 Props + 自定义事件
兄弟组件通信 全局事件总线
跨级组件通信 provide/inject 或 Vuex
内容分发 插槽(默认/具名/作用域)
属性透传 $attrs + $listeners
跨框架通信 PubSub

5.3 注意事项清单

  • ✅ 使用全局事件总线后,务必在 beforeDestroy 中解绑事件
  • ✅ PubSub需要在组件销毁前取消订阅
  • ✅ 作用域插槽的子组件通过插槽属性传递数据,父组件通过 scope 接收
  • $children 不保证顺序,推荐使用 ref 替代
  • $attrs 不包含被props声明的属性
  • ✅ 大型项目推荐使用Vuex管理状态,而非事件总线

从基础的Props到高级的事件总线和插槽,你已经掌握了Vue2组件通信的完整知识体系。在实际开发中,根据具体场景选择合适的通信方式,能让你的代码更加清晰和高效。

相关推荐
kyriewen2 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马2 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu2 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss
IT_陈寒3 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg3 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude
默_笙3 小时前
❗ 给金鱼装记忆(上):从白板到笔记本,AI 的短期记忆生存指南
前端·javascript
酷酷的逗逗乐3 小时前
前端转 Agent 开发 · 第五节:Memory 会话记忆
前端·程序员
掘金挖土3 小时前
前端手摸手跑路之 AI 应用开发(二)
前端·后端
兜里只有三分钱~4 小时前
翻卡_3D 的仪式感,和三个让它穿帮的写法
javascript·css·3d