Vue组件间的通信还可借助第三方库来实现。而pubsub-js就是一种实现了消息的订阅与发布的库。使用pubsub-js可以实现任意组件间的通信。
安装pubsub-js:
1npm i pubsub-js
在需要发布或订阅消息的组件中引入pubsub-js:
1import pubsub from 'pubsub-js'
发布消息
pubsub-js发布消息可以使用pubsub.publish()方法:
1pubsub.publish(msgName, data)
msgName:发送消息的名称,字符串类型。data:发送的消息(数据),类型任意。
注:
pubsub.publish()与$bus.$emit()方法不一样。pubsub.publish()仅有一个data参数作为消息进行发送。而$bus.$emit()的参数数量是可变的,从第2个开始的参数都可作为消息发送。
订阅消息
pubsub-js订阅消息可以使用pubsub.subscribe()方法:
1pubsub.subscribe(msgName, callback)
-
msgName:订阅的消息名称,字符串类型。 -
callback:收到消息时,执行的回调函数。当
pubsub.subscribe()的回调函数使用一般的function形式定义时,回调函数中的this指向的是undefined。而在Vue中定义的回调一般是Lambda表达式。所以使用Lambda表达式直接在
pubsub.subscribe()中定义回调函数即可。 -
返回值:返回当前订阅的ID值。
取消订阅
pubsub-js取消订阅消息可以使用pubsub.unsubscribe()方法:
1pubsub.unsubscribe(subId)
subId:订阅的ID值。即,在调用pubsub.subscribe()时返回的ID值。
与全局事件总线的对比
- 全局事件总线是Vue自带的一个模型,无需引入第三方库。
- Pubsub是第三方库,其事件的订阅与发布无法在Vue开发者工具中查看。
- 全局事件总线的功能整体上与消息的订阅发布并无太大差别。
挂载 Pubsub
使用Pubsub时,可以像安装全局事件总线时一样,将Pubsub挂载到Vue实例上,这样在组件中使用Pubsub时就无需多次重复引入Pubsub。
1import pubsub from 'pubsub-js'
2import Vue from 'vue'
3/* ... */
4
5new Vue({
6 /* ... */
7 beforeCreate() {
8 Vue.prototype.$pubsub = pubsub
9 },
10 /* ... */
11}).$mount('#app')
-
发布消息:
1this.$pubsub.publish(msgName, data)this指Vue组件实例,下同。 -
订阅消息:
1const subId = this.$pubsub.subscribe(msgName, callback) -
取消订阅
1this.$pubsub.unsubscribe(subId)
消息订阅发布案例
将组件自定义事件中的todo-list案例修改成使用消息订阅与发布实现,并且增加了编辑功能。
注:下方注释内容为
...(即<!-- ... -->或/* ... */)的部分,代表与原先组件自定义事件中的案例内容相同。
main.js
1import Vue from 'vue'
2import pubsub from 'pubsub-js'
3import App from './App.vue'
4
5Vue.config.productionTip = false
6
7new Vue({
8 render: h => h(App),
9 beforeCreate() {
10 Vue.prototype.$pubsub = pubsub
11 },
12}).$mount('#app')
todo-list-itme.vue
1<template>
2<li>
3 <label>
4 <input type="checkbox" :checked="isCompleted" @change="handleCheck"/>
5 <span v-show="!isEdit">{{name}}</span>
6 <input
7 v-show="isEdit"
8 type="text"
9 v-model.lazy="todoName"
10 ref="input"
11 @keyup.enter="handleBlur"
12 @blur="handleBlur"
13 />
14 </label>
15 <button class="btn btn-danger" @click="handleDelete">删除</button>
16 <button
17 v-show="!isEdit"
18 class="btn btn-edit"
19 @click="handleEdit"
20 >
21 编辑
22 </button>
23</li>
24</template>
25
26<script>
27export default {
28 name: 'todo-list-item',
29 props: {/* ... */},
30 data() {
31 return {
32 isEdit: false,
33 }
34 },
35 computed: {
36 todoName: {
37 get() {
38 return this.name
39 },
40 set(name) {
41 if (!name.trim()) return alert('事件名称不能为空!') // 控制输入不能为空
42 this.$pubsub.publish('update-todo', {id: this.id, name})
43 }
44 },
45 },
46 methods: {
47 // 勾选或取消勾选
48 handleCheck() {
49 // 通知 App.vue 将对应的 todo 对象的 isCompleted 取反
50 this.$pubsub.publish('check-todo', this.id)
51 },
52 // 删除
53 handleDelete() {
54 if (confirm(`是否确定删除${this.name}?`)) {
55 this.$pubsub.publish('remove-todo', this.id)
56 }
57 },
58 // 进入编辑
59 handleEdit() {
60 this.isEdit = true
61 // 模板重新解析完成后才自动获取焦点
62 // $nextTick() 指定的回调,会在下次DOM更新完成之后才执行
63 this.$nextTick(() => {
64 this.$refs.input.focus()
65 })
66 },
67 // 失去焦点
68 handleBlur() {
69 this.isEdit = false
70 }
71 },
72}
73</script>
74
75<style scoped>
76/* ... */
77</style>
App.vue
1<template>
2<div id="root">
3 <!-- ... -->
4</div>
5</template>
6
7<script>
8import TodoHeader from './components/todo-header.vue'
9import TodoFooter from './components/todo-footer.vue'
10import TodoMain from './components/todo-main.vue'
11
12export default {
13 name: 'App',
14 components: {
15 TodoHeader,
16 TodoFooter,
17 TodoMain,
18 },
19 data() {
20 return {
21 // 将 todos 列表定义在 App.vue 中
22 todos: JSON.parse(localStorage.getItem('todos')) || [],
23 subIds: {},
24 }
25 },
26 watch: {
27 todos: {
28 deep: true,
29 handler(value) {/* ... */},
30 },
31 },
32 computed: {
33 // 计算被选 todo 的总数
34 completedTotal() {/* ... */},
35 // 计算 todos 总数
36 total() {/* ... */},
37 },
38 methods: {
39 // 添加一个 todo
40 addTodo(todo) {/* ... */},
41 // 勾选或取消一个 todo(使用 '_' 作为冗余参数的占位符)
42 checkTodo(_, id) {/* ... */},
43 // 删除一个 todo(使用 '_' 作为冗余参数的占位符)
44 deleteTodo(_, id) {/* ... */},
45 // 选择所有或取消选择所有
46 checkAllTodo(checked) {
47 this.todos.forEach(todo => todo.isCompleted = checked)
48 },
49 // 清除所有已完成的 todo
50 clearAllCompletedTodos() {/* ... */},
51 },
52 mounted() {
53 this.subIds.checkTodo = this.$pubsub
54 .subscribe('check-todo', this.checkTodo)
55 this.subIds.removeTodo = this.$pubsub
56 .subscribe('remove-todo', this.deleteTodo)
57 this.subIds.updateTodo = this.$pubsub
58 .subscribe('update-todo', this.updateTodo)
59 },
60 beforeDestroy() {
61 // 取消订阅所有消息
62 Object.values(this.subIds)
63 .forEach(subId => this.$pubsub.unsubscribe(subId))
64 this.subIds = {}
65 },
66}
67</script>
68
69<style>
70/* ... */
71</style>
评论