Vue10-全局事件总线+消息订阅和发布
Vue10-全局事件总线+消息订阅和发布
1.全局事件总线
- 全局事件总线是一种组件之间通信的方式,可以实现任意组件之间的通信。
- 安装全局事件总线。
new Vue({
...
beforeCreate() {
Vue.prototype.$bus = this; // 安装全局事件总线,将$bus指定为vm。
}
})
- 使用全局事件总线。
// 1 接受数据的组件。
// A组件想接受到其他组件的数据,就在A组件内给$bus绑定自定义事件,事件的回调留在A中。
mounted() {
// 绑定xxx事件,xxx事件触发时调用this.demo函数。
this.$bus.$on('xxx', this.demo);
this.$bus.$on('xxx', 箭头函数);
}
// 2 提供数据的组件。
// 调用bus上的xxx事件,并且传递数据。
this.$bus.$emit('xxx', '数据');
- 销毁全局事件。在$bus中绑定的事件会被所有VueComponent实例所共享,所有最好在接受数据的组件的beforeDestroy()中,使用$off()解绑当前组件所绑定的事件。
2.全局事件总线-Student和School组件直接的通信
- main.js。
import Vue from 'vue'
import App from './App'
// 获取VueComponent
const demo = Vue.extend({});
// 创建VueComponent实例对象,VueComponent实例对象中有$on,$emit等操作事件的方法。
const d = new demo();
// 将VueComponent实例对象赋值给Vue元素对象的x属性,任何VueComponent组件都可以访问x,
// 从而来操作事件。
Vue.prototype.x = d;
// 一般绑定全局事件总线在Vue声明周期钩子函数beforeCreate()上。
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this; // 安装全局事件总线
}
})
- Student.vue。
<template>
<div class="student">
<p>学生名 {{name}}</p>
<p>学生地址 {{address}}</p>
<button>发送学生名</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
msg: '学生信息',
name: 'tom',
address: 'A01'
}
},
mounted() {
// x是VueComponent组件实例对象,并且在main.js中被绑定到Vue的原型上。
// this.x.$on('hello', yyy); 为x绑定hello事件,并且在hello事件被触发时调用yyy箭头函数。
this.x.$on('hello', (data) => {
console.log('getName Student收到数据 ', data)
});
this.$bus.$on('getName', (data) => {
console.log('getName Student收到数据 ', data)
});
},
beforeDestroy() {
// $bus是Vue原型上的属性,所以会被所有的VueComponent组件实例所共享,
// 所以这里在School组件被销毁时,将在School组件内为$bus上绑定的事件销毁。
this.$bus.$off('getName');
}
}
</script>
<style scoped>
.student {
background-color: red;
padding: 5px;
margin: 3px;
}
</style>
- School.vue。
<template>
<div class="school">
<p>学校名 {{name}}</p>
<p>学校地址 {{address}}</p>
<button @click="sendSchoolName">发送学校名</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
msg: '学校信息',
name: '学校名',
address: '学校',
}
},
methods: {
sendSchoolName() {
// 触发x绑定的hello事件,并且将数据{大学校}传递给函数。
// x的hello事件在Student组件上绑定。
this.x.$emit('hello', '大学校');
this.$bus.$emit('getName', '$bus发送数据');
}
},
}
</script>
<style scoped lang="css">
.school {
background-color: orange;
padding: 5px;
margin: 3px;
}
</style>
3.TodoList案例添加全局事件总线
- main.js中给Vue元素上添加$bus属性。
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this;
}
})
- App.vue中绑定并且解绑事件。
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo" />
<MyList :todos="todos" />
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearDoneTodo="clearDoneTodo" />
</div>
</div>
</div>
</template>
<script>
...
export default {
mounted() {
// 绑定事件。
this.$bus.$on('checkTodo', this.checkTodo);
this.$bus.$on('deleteTodo', this.deleteTodo);
},
beforeDestroy() {
// 解绑事件。
this.$bus.$off('checkTodo');
this.$bus.$off('deleteTodo');
}
}
</script>
- MyItem.vue中调用全局事件。
<script>
export default {
name: "MyItem",
methods: {
handlerChange(id) {
this.$bus.$emit('checkTodo',id)
},
handlerDelete(id) {
if (confirm('确认删除')) {
this.$bus.$emit('deleteTodo', id);
}
}
},
props: ['todo']
}
</script>
4.消息订阅和发布
-
消息订阅和发布是一种组件间通信的方式,适用于任何组件间的通信。
-
原生JavaScript不能支持消息订阅和发布,所有需要引入pubsub-js来支持消息订阅和发布,pubsub-js可以支持任何前端框架的消息订阅和发布。
-
pubsub-js的使用步骤。
// 1 安装pubsub-js。
npm i pubsub-js
// 2 组件中引入pubsub-js。
import pubsub from 'pubsub-js'
// 3 接受数据的组件订阅消息。
mounted() {
this.pubId = pubsub.subscribe('hello', (messageName, data) => {
console.log(this); // VueComponent
console.log('Student组件', messageName, data);
})
}
// 4 提供数据的组件发布消息。
pubsub.publish('hello', '学校名不知道')
// 5 订阅消息的组件销毁时取消消息的订阅。
beforeDestroy() {
// 通过订阅消息返回的id来取消消息的订阅。
pubsub.unsubscribe(this.pubId);
}
5.消息订阅和发布实现Student和School组件之间的通信
- Student.vue订阅消息。
<template>
<div class="student">
<p>学生名 {{name}}</p>
<p>学生地址 {{address}}</p>
<button>发送学生名</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "Student",
data() {
return {
msg: '学生信息',
name: 'tom',
address: 'A01'
}
},
mounted() {
// 订阅hello消息,当hello消息被发布时,触发第二个参数逻辑。
// subscribe有两个参数,第一个参数是订阅的消息名。
// 第二个参数是一个函数,是订阅消息发布时执行的函数。
// 函数有两个参数,messageName是消息名,data是消息的数据。
// subscribe()函数返回一个id,可以用来取消消息的订阅。
// 当subscribe的第二个参数为普通函数时,this为undefined。
// this.pubId = pubsub.subscribe('hello', function (messageName, data) {
// console.log(this); // undefined
// console.log('Student组件', messageName, data);
// });
// 当subscribe的第二个参数为箭头函数时,this为VueComponent实例对象。
this.pubId = pubsub.subscribe('hello', (messageName, data) => {
console.log(this); // VueComponent
console.log('Student组件', messageName, data);
})
},
beforeDestroy() {
// 通过订阅消息返回的id来取消消息的订阅。
pubsub.unsubscribe(this.pubId);
}
}
</script>
<style scoped>
.student {
background-color: red;
padding: 5px;
margin: 3px;
}
</style>
- School.vue中发布消息。
<template>
<div class="school">
<p>学校名 {{name}}</p>
<p>学校地址 {{address}}</p>
<button @click="sendSchoolName">发送学校名</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "School",
data() {
return {
msg: '学校信息',
name: '学校名',
address: '学校',
}
},
methods: {
sendSchoolName() {
// 发布消息。
pubsub.publish('hello', '学校名不知道')
}
},
}
</script>
<style scoped lang="css">
.school {
background-color: orange;
padding: 5px;
margin: 3px;
}
</style>
6.TodoList案例添加消息订阅和发布
- App.vue中订阅消息。
<script>
...
export default {
...
mounted() {
this.$bus.$on('checkTodo', this.checkTodo);
// this.$bus.$on('deleteTodo', this.deleteTodo);
// 订阅deleteTodo消息。
this.pubId = pubsub.subscribe('deleteTodo', this.deleteTodo);
},
beforeDestroy() {
this.$bus.$off('checkTodo');
//this.$bus.$off('deleteTodo');
// 通过id取消订阅deleteTodo。
pubsub.unsubscribe(this.pubId);
}
}
</script>
- MyItem.vue中发布消息。
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyItem",
methods: {
handlerChange(id) {
this.$bus.$emit('checkTodo',id)
},
handlerDelete(id) {
if (confirm('确认删除')) {
// this.$bus.$emit('deleteTodo', id);
pubsub.publish('deleteTodo', id);
}
}
},
props: ['todo']
}
</script>
7.TodoList案例的MyItem组件添加编辑按钮
- this.$nextTick(函数)中的函数会在下一次DOM更新完成后执行。
- $nextTick的使用时间。当数据更新完成后,要基于更新后的新DOM进行的某些操作,就需要在$nextTick所指定的回调函数中执行。
- MyItem.vue。
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handlerChange(todo.id)"/>
<!-- 当todo不需要编辑时显示span,当todo需要编辑时显示input。 -->
<span v-show="!todo.isEdit">{{ todo.name }}</span>
<!-- @blur失去焦点事件。 -->
<input type="text"
v-show="todo.isEdit"
:value="todo.name"
@blur="updateTodo(todo, $event)"
ref="inputName">
</label>
<button class="btn btn-danger" @click="handlerDelete(todo.id)">删除</button>
<button class="btn btn-danger" @click="handlerEdit(todo)">编辑</button>
</li>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyItem",
methods: {
handlerChange(id) {
this.$bus.$emit('checkTodo',id)
},
handlerDelete(id) {
if (confirm('确认删除')) {
// this.$bus.$emit('deleteTodo', id);
pubsub.publish('deleteTodo', id);
}
},
// 编辑Todo,如果Todo没有isEdit属性就添加isEdit属性;
// 如果有isEdit属性,就将值修改为true。
handlerEdit(todo) {
if (todo.hasOwnProperty('isEdit')) {
todo.isEdit = true;
} else {
// todo.isEdit = true;
// 直接使用todo.isEdit = true;给todo添加isEdit属性,会导致
// isEdit属性不会被监视,修改了也不会发生改变,所以需要使用$set添加。
this.$set(todo, 'isEdit', true);
}
// 如果直接通过focus()让其获取焦点,此时数据发生了变化,
// 但是DOM还没有被更新显示的页面,所以focus是没有效果的。
//this.$refs.inputName.focus();
// $nextTick中的函数会在下一次DOM更新完成后执行指定的回调。
// 保证DOM刷新到页面后再执行focus获取焦点的操作。
this.$nextTick(function () {
this.$refs.inputName.focus();
})
},
// 当输入框失去焦点,就去修改todo中name的值。
updateTodo(todo, event) {
const value = event.target.value;
if (value.trim() === '') {
return alert('输入不能为空');
}
this.$bus.$emit('updateTodo', todo.id, value);
todo.isEdit = false;
}
},
props: ['todo']
}
</script>