Vue中$emit和$on的实现原理


在Vue中采用了发布订阅模式,典型的兄弟组件间的通信$on$emit

发布订阅模式:(订阅者、发布者、信号中心)

一个发布者$emit发布一个事件到信号中心 eventBus ,订阅者们 $on 通过信号中心收到该事件,进行处理。

Vue.prototype.$emit的作用是循环执行当前 vm (组件实例)的 _events 属性内某个 event (事件名)对应的事件 回调 列表。也就是触发事件。

来看看源码,以下代码来自 vue@2.6.14.global.js

Vue.prototype.$emit = function (event) {
    var vm = this;
    {
        var lowerCaseEvent = event.toLowerCase();
        //...
    }
    var cbs = vm._events[event];
    if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        var args = toArray(arguments, 1);
        var info = 'event handler for "' + event + '"';
        for (var i = 0, l = cbs.length; i < l; i++) {
            invokeWithErrorHandling(cbs[i], vm, args, vm, info);
        }
    }
    return vm;
};

vm_events 属性内的对 event 的回调方法收集全部是通过Vue.prototype.$on方法收集的。即事件监听。

Vue.prototype.$on = function (event, fn) {
    var vm = this;
    if (Array.isArray(event)) {
        for (var i = 0, l = event.length; i < l; i++) {
            vm.$on(event[i], fn);
        }
    } else {
        (vm._events[event] || (vm._events[event] = [])).push(fn);
        // optimize hook:event cost by using a boolean flag marked at registration
        // instead of a hash lookup
        if (hookRE.test(event)) {
            vm._hasHookEvent = true;
        }
    }
    return vm;
};

通过 $emit 实现父子组件通讯的第一个步骤:在父组件内,对子组件的占位符标签上绑定一个自定义事件回调。根据传入的事件名从当前实例的_events属性(即事件中心)中获取到该事件名所对应的回调函数cbs,然后再获取传入的附加参数args,由于cbs是一个数组,所以遍历该数组,拿到每一个回调函数,执行回调函数并将附加参数args传给该回调。这个绑定动作最终将通过子组件的 $on 方法将回调进行收集。

在这里模拟一个自定义事件 $on和$emit事件

//utils 新建里 event.js
export default {
    map: {},
    $emit(name, params) {
        if (this.map[name] == null) {
            console.log("没有找到关于" + name + "的事件,无法触发");
        } else {
            this.map[name].detail = params;
            window.dispatchEvent(this.map[name]);
        }
    },
    $on(name, work) {
        let myEvent = new Event(name);
        this.map[name] = myEvent;
        window.addEventListener(name, event => {
            console.log("得到数据为:", event.detail);
            work(this.map[name].detail);
        });
    },
};

main.js中注入(Vue3中使用eventBus.$oneventBus.$emit功能)

import { createApp } from "vue";
import App from "./App.vue";
import myEvent from "./utils/event.js"; //引入

const app = createApp(App);
app.config.globalProperties.$event = myEvent; //绑定到全局中

app.mount("#app");

vue3中使用

import { getCurrentInstance, onMounted } from "vue";
const { proxy } = getCurrentInstance();
const $event = proxy.$event;
//以上为公共,每个页面都需要注册;
//child页面
const sendData = () => {
    $event.$emit("changeStartMenu", 11111);
};

//根页面
onMounted(() => {
    $event.$on("changeStartMenu", res => {
        console.log(res, "触发成功");
    });
});

补充:getCurrentInstance只能在setup或 生命周期钩子 中调用。

如需在setup或 生命周期钩子 外使用,请先在setup中调用getCurrentInstance()获取该实例后再使用。


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录