理论基础
理论知识点1:中间人
如果我告诉大家,vue官方在Vue构造函数原型上提供了$on()发布,$emit()接收,
$off()删除三个api,如果大家知晓一点设计模式的话,实际上这就是一个标准的
消息发布与订阅设计模式。此时Vue构造函数实例化出来的对象就具备了成为组
件通信中间人的条件。
额外知识补充
为大家简单介绍一下消息发布与订阅模式吧!
发布者调用一个方法实例.$on()将消息添加到消息队列中,并给当前这个消息添加
标识(此过程相当于将制片方电影放在电影院的影片集中,电影的名称就是标识)。
订阅者调用实例.$emit()从消息队列中拿到对应的消息(此过程相当于观众购买对应的电
影片观看电影)。发布者调用实例.$off将消息从消息队列中删除(此过程相当于电影院
下架该电影)
额外知识补充分隔线-----------------------------------------
理论知识点1验证:
mounted(){
console.log(this);
console.log(this.__proto__);
}
理论知识点2:共享中间人
那么问题来了我们怎么可以在所有组件实例中访问到这个中间人?
想不出来没关系,我给大家一个提示。所有的vue组件都是通过构造函数Vue实例化
出来的!!!
那么我们是不是可以将中间人挂载到构造函数Vue的原型上面?这样每一个通过new Vue({})
实例化出来的vue组件都可以访问到这个中间人了?
理论知识点3:函数传值
这个理论知识我在改专栏中的详细vue2组件通信(二)——子传父自定义事件中详细讲解过。
我们可以通过调用函数给函数传递实参来传递数据,但是要注意数据的引用类型。
理论存在实践开始
既然我们要在所有组件实例化之前就得为Vue构造函数的原型上加上我们的中间人,我们
应该在整个项目执行的起始文件也就是主文件main.js中添加。
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// new Vue()就是中间人
Vue.prototype.$bus = new Vue();
new Vue({
render: (h) => h(App),
}).$mount("#app");
<template>
<div>
Son
</div>
</template>
<script>
export default {
name: "web-son",
mounted(){
console.log(this);
console.log(this.__proto__);
console.log(this.$bus);
}
}
</script>
<style scoped>
</style>
优化1:为项目减少一个vue组件的内存消耗
不知道大家是否明白Vue.prototype.$bus = new Vue();意味着我们为整个项目
增加了一个无用的vue组件实例。我们整个项目中这么多的vue组件实例,为什
么我们还要再new一个呢?App组件实例难道不是一个很好的选择吗?
代码优化
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
beforeCreate() {
Vue.prototype.$bus = this;
}
}).$mount("#app");
为什么可以这么写,后续我在vue专栏里面单独开一篇博客和大家一块儿讨论main.js
主文件的一些细节点。
优化2:减轻$bus属性值的体积
因为$bus属性值是App组件实例对象,这个对象有着vue官方给与的众多属性和方法,
但是我们自己清楚我们仅仅只是需要$on,$emit,$off这三个方法罢了。那么为什么我们
不自己写一个呢?不就是一个消息发布订阅设计模式吗?不会就去学,干就完事了。
费了九牛二虎之力,我们终于实现了。
消息发布与订阅模式es6 class版本
class Bus {
constructor() {
// 消息队列
this.messageQueue = {};
}
$on(name, fn) {
/* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
一个空数组 */
this.messageQueue[name] = this.messageQueue[name] || [];
this.messageQueue[name].push(fn);
}
$emit(name, args) {
if (this.messageQueue[name]) {
/* 循环对应标识的函数数组,并将所有的参数传递给数组中
每一项函数执行 */
this.messageQueue[name].forEach((cb) => cb(args));
}
}
$off(name) {
if (name) {
// 删除消息队列中对应的标识属性
delete this.messageQueue[name];
} else {
/* 如果直接调用$bus.$off()不传入name,此时name为undefined,
消息队列清空 */
this.messageQueue = {};
}
}
}
消息发布与订阅模式es5版本
const Bus = function() {
let messageQueue = {};
Bus.prototype.$on = function(name, fn) {
/* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
一个空数组 */
messageQueue[name] = messageQueue[name] || [];
messageQueue[name].push(fn);
};
Bus.prototype.$emit = function(name, args) {
if (messageQueue[name]) {
/* 循环对应标识的函数数组,并将所有的参数传递给数组中
每一项函数执行 */
messageQueue[name].forEach((cb) => cb(args));
}
};
Bus.prototype.$off = function(name) {
if (name) {
// 删除消息队列中对应的标识属性
delete messageQueue[name];
} else {
// 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
messageQueue = {};
}
};
};
如果不想自己手写,你还可以选择当一个调包侠
借助消息订阅与发布第三方库实现
先下载第三方包 npm install pubsub-js --save
消息订阅者
import pubsub from 'pubsub-js'
mounted() {
this.pubsubGSN = pubsub.subscribe('getSchoolName',(msgName,data) => {
// 一定要使用箭头函数的形式,不然this指向就是undefined,或者将回调函数写在methodes中
console.log('感谢'+ msgName + '发布者的发布');
console.log(this.name + '在' + data + '读书');
})
},
beforeDestroy() {
// this.$bus.$off('getSchoolName')
pubsub.unsubsctibe(this.pubsubGSN)
},
消息的发布者
methods: {
sendSchoolName(){
// this.$bus.$emit('getSchoolName',this.name)
pubsub.publish('getSchoolName',this.name)
}
},
最终代码与实现效果
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// class Bus {
// constructor() {
// // 消息队列
// this.messageQueue = {};
// }
// $on(name, fn) {
// /* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
// 一个空数组 */
// this.messageQueue[name] = this.messageQueue[name] || [];
// this.messageQueue[name].push(fn);
// }
// $emit(name, args) {
// if (this.messageQueue[name]) {
// /* 循环对应标识的函数数组,并将所有的参数传递给数组中
// 每一项函数执行 */
// this.messageQueue[name].forEach((cb) => cb(args));
// }
// }
// $off(name) {
// if (name) {
// // 删除消息队列中对应的标识属性
// delete this.messageQueue[name];
// } else {
// // 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
// this.messageQueue = {};
// }
// }
// }
const Bus = function() {
let messageQueue = {};
Bus.prototype.$on = function(name, fn) {
/* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
一个空数组 */
messageQueue[name] = messageQueue[name] || [];
messageQueue[name].push(fn);
};
Bus.prototype.$emit = function(name, args) {
if (messageQueue[name]) {
/* 循环对应标识的函数数组,并将所有的参数传递给数组中
每一项函数执行 */
messageQueue[name].forEach((cb) => cb(args));
}
};
Bus.prototype.$off = function(name) {
if (name) {
// 删除消息队列中对应的标识属性
delete messageQueue[name];
} else {
// 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
messageQueue = {};
}
};
};
new Vue({
render: (h) => h(App),
beforeCreate() {
Vue.prototype.$bus = new Bus();
},
}).$mount("#app");
App.vue
<template>
<div>
<Son></Son>
</div>
</template>
<script>
import Son from "./components/Son.vue";
export default {
name: "App",
components: {
Son,
},
beforeCreate(){
this.$bus.$on("sendValue",value => {
console.log(value);
})
},
beforeDestroy(){
this.$bus.$off("sendValue");
}
};
</script>
<style>
</style>
Son.vue
<template>
<div>Son</div>
</template>
<script>
export default {
name: "web-son",
mounted() {
console.log(this);
console.log(this.__proto__);
console.log(this.$bus);
this.$bus.$emit("sendValue", "data");
},
};
</script>
<style scoped>
</style>
最终效果