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