目录
一、全局事件总线(GlobalEventBus)
1.1 全局事件总线前需了解的内容
1.1.1 Vue原型对象上包含事件处理的方法
- $on(eventName, listener):绑定自定义事件,监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数
- $emit(eventName, data):触发当前实例上的事件。附加参数都会传给监听器回调
- $off(eventName):移除自定义事件监听器
- 如果没有提供参数,则移除所有的事件监听器
- 如果只提供了事件,则移除该事件所有的监听器
- 如果同时提供了事件与回调,则只移除这个回调的监听器
- $once(eventName, listener):监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除
1.1.2 重要的内置关系
所有组件实例对象的 原型对象的 原型对象 是 Vue的原型对象
- 所有组件对象都能看到 Vue原型对象上的属性和方法
1.2 全局事件总线
1. 一种组件间的通信方式,适用于任意组件间通信
2. 安装全局事件总线(在main.js中):
new Vue({
.....
beforeCreate(){
Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm
},
.....
})
3. 使用事件总线
(1)接收数据:A组件想要接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods(){
demo(data){.....}
}
.....
mounted(){
this.$bus.$on('xxx', this.demo) // xxx指的是自定义事件,demo指的是回调
}
(2)提供数据:
this.$bus.$emit('xxx', 数据) // xxx指的是自定义事件
4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
beforeDestroy(){
this.$bus.$off('xxx') // xxx指的是自定义事件
}
二、TodoList案例_全局事件总线
首先在main.js中安装全局事件总线
// 引入Vue
import Vue from "vue";
// 引入App
import App from "./App.vue";
Vue.config.productionTip = false;
// 创建vm
new Vue({
el: "#app",
render: (h) => h(App),
// 安装全局事件总线
beforeCreate() {
Vue.prototype.$bus = this;
},
});
清除App.vue 和 UserList.vue 中组件绑定的自定义事件,以及 UserList.vue 和UserItem.vue 中 props属性 接收的自定义事件
App.vue
<!-- <UserList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></UserList> -->
<UserList :todos="todos"></UserList>
UserList.vue
<!-- <UserItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
></UserItem> -->
<UserItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
></UserItem>
在App.vue中采用全局总线方法绑定自定义事件,以及设置vm销毁时解绑自定义事件
mounted() {
// 全局事件总线 - 绑定自定义事件
this.$bus.$on("checkTodo", this.checkTodo);
this.$bus.$on("deleteTodo", this.deleteTodo);
},
beforeDestroy() {
// 全局事件总线 - 解绑自定义事件
this.$bus.$off("checkTodo");
this.$bus.$off("deleteTodo");
},
在UserItem.vue中 使用全局事件总线触发当前实例上的事件
methods: {
// 勾选或取消勾选
handleCheck(id) {
// 通过App组件将对应的todo对象的done值取反
// this.checkTodo(id);
// 全局事件总线 - 触发当前实例上的事件
this.$bus.$emit("checkTodo", id);
},
handleDelete(id) {
if (confirm("您确认删除吗?")) {
// this.deleteTodo(id);
// 全局事件总线 - 触发当前实例上的事件
this.$bus.$emit("deleteTodo", id);
}
},
},
由图示可看出,采用全局事件总线,成功调用了 checkTodo自定义事件 和 deleteTodo自定义事件,且不需要让UserList.vue充当 App.vue 和 UserItem.vue之间通行的媒介,也不需要在组件标签上绑定多个自定义事件
三、消息订阅与发布
1. 一种组件间的通信的方式,适用于任意组件间通信
2. 使用步骤:
(1)安装pubsub(在终端输入):
npm i pubsub-js
(2)引入:
import pubsub from 'pubsub-js'
(3)接收数据:A组想接收数据,则在A组中订阅消息,订阅的回调留在A组件自身(对应绑定事件监听)
methods:{
demo(data){...}
}
...
mounts(){
// 回调写在methods中
this.pid = pubsub.subscribe('msgName',this.demo) // 订阅消息
// 回调直接写在内部
this.pid = pubsub.subscribe('msgName',function(msgName,data){})
// 一般使用箭头函数,确保this指向vc
this.pid = pubsub.subscribe('msgName',(msgName,data)=>{})
}
(4)提供数据,即发布消息,触发订阅的回调函数调用:
pubsub.publish('msgName', data)
(5)取消消息的订阅,最好在beforeDestroy钩子中去取消订阅
pubsub.unsubscribe(pid)
四、TodoList案例_消息订阅与发布
在TodoList案例中,UserItem组件内的删除功能采用 消息订阅与发布 的方式
在App.vue 中引入pubsub,并订阅消息,以及订阅的回调
<script>
import pubsub from "pubsub-js";
import UserHeader from "./components/UserHeader.vue";
import UserList from "./components/UserList.vue";
import UserFooter from "./components/UserFooter.vue";
export default {
name: "App",
components: { UserHeader, UserList, UserFooter },
data() {
...
},
methods: {
...
// 删除一个todo
deleteTodo(_, id) {
// '_'起到占位作用
this.todos = this.todos.filter((todo) => todo.id !== id);
},
...
},
watch: {
...
},
mounted() {
// 全局事件总线 - 绑定自定义事件
this.$bus.$on("checkTodo", this.checkTodo);
// 订阅 - 接收数据
this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo);
},
beforeDestroy() {
// 全局事件总线 - 解绑自定义事件
this.$bus.$off("checkTodo");
// 解除订阅
pubsub.unsubcribe("pubId");
},
};
</script>
在UserItem.vue 中 引入pubsub,并发布消息
<script>
import pubsub from "pubsub-js";
export default {
name: "UserItem",
props: ["todo"],
methods: {
// 勾选或取消勾选
handleCheck(id) {
// 通过App组件将对应的todo对象的done值取反
// this.checkTodo(id);
// 全局事件总线 - 接收数据
this.$bus.$emit("checkTodo", id);
},
handleDelete(id) {
if (confirm("您确认删除吗?")) {
// this.deleteTodo(id);
// 全局事件总线 - 接收数据
// this.$bus.$emit("deleteTodo", id);
// 发布消息
pubsub.publish("deleteTodo", id);
}
},
},
};
</script>
由图示可以看出,删除功能可以顺利完成。与全局事件总线不同的是,在Vue开发者工具中,看不到 订阅或发布了哪些消息,因为pubsub是第三方库。
五、TodoList案例 - 添加编辑功能
5.1 编辑功能
首先在UserItem.vue中添加编辑按钮,并在App.vue中设置样式
5.1.1 添加编辑按钮
UserItem.vue
<button class="btn btn-edit">编辑</button>
App.vue
.btn-edit {
color: #fff;
background-color: skyblue;
border: 1px solid rgb(65, 155, 190);
margin-right: 5px;
}
5.1.2 实现编辑功能
点击编辑按钮,使输入框显示出来,并且实现修改输入框内容,在离开焦点时改变数据
App.vue 内
- 定义了更新事件的回调 updateTodo,并对其绑定 以及 销毁后的解绑
- 对编辑按钮的样式的编写
methods: {
...
// 更新功能
updateTodo(id, title) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.title = title;
});
},
...
},
mounted() {
// 全局事件总线 - 绑定自定义事件
...
// 编辑
this.$bus.$on("updateTodo", this.updateTodo);
...
},
beforeDestroy() {
...
// 解绑编辑
this.$bus.$off("updateTodo");
...
},
UserItem.vue内
- 添加编辑按钮以及点击编辑按钮出现的输入框,并用v-show限制其是否显示
- 编辑功能handleEdit
- 输入框失去焦点后的回调 handleBlur(真正执行修改逻辑)
添加编辑按钮以及点击编辑按钮出现的输入框
<template>
<li>
<label>
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<!-- 输入框 -->
<!-- $event:获取到该事件的事件对象 -->
<input
type="text"
:value="todo.title"
v-show="todo.isEdit"
@blur="handleBlur(todo, $event)"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<!-- 编辑按钮 -->
<button
class="btn btn-edit"
@click="handleEdit(todo)"
v-show="!todo.isEdit"
>
编辑
</button>
</li>
</template>
编辑功能handleEdit 以及 输入框失去焦点后的回调 handleBlur
methods: {
...
// 编辑功能
handleEdit(todo) {
// if判断条件 'isEdit' in todo 或 Object.prototype.hasOwnProperty.call(todo, "isEdit")
if (Object.prototype.hasOwnProperty.call(todo, "isEdit")) {
// todo身上有 isEdit属性
// console.log("todo身上有isEdit属性");
todo.isEdit = true;
} else {
// todo身上没有 isEdit属性
this.$set(todo, "isEdit", true);
}
},
// 失去焦点回调(真正执行修改逻辑)
handleBlur(todo, e) {
todo.isEdit = false;
if (!e.target.value.trim()) return alert("输入不能为空");
this.$bus.$emit("updateTodo", todo.id, e.target.value);
},
},
5.1.3 存在的bug
由图示可看出,在不手动点击输入框后,在未手动获得输入框的焦点时,进行了其他操作,输入框仍会显示
5.1.4 bug处理 - $nextTick
- 语法:this.$nextTick(回调函数)
- 作用:在下一次DOM更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
对于 5.1.3 中bug的处理,可以通过点击按钮后,输入框会自动获取焦点
首先为输入框设置ref属性
<input
type="text"
:value="todo.title"
v-show="todo.isEdit"
@blur="handleBlur(todo, $event)"
ref="inputTitle"
/>
不能直接在编辑功能handelEdit内添加 this.$refs.inputTitle.focus() 的原因:
- 因为第一次解析模板时,使用了v-show在控制输入框是否显示,所以在 执行到 this.$refs.inputTitle.focus() 时,input输入框还是处于隐藏状态,并不能获取焦点
如何解决该问题?
方法一:设置定时器
// 编辑功能
handleEdit(todo) {
...
// 定时器
setTimeout(() => {
this.$refs.inputTitle.focus();
}, 500);
},
方法二:$nextTick,$nextTick所指定的回调,会在DOM节点更新完毕之后再执行(推荐写法)
// 编辑功能
handleEdit(todo) {
...
this.$nextTick(function () {
this.$refs.inputTitle.focus();
});
},
由图示可以看出,在点击编辑按钮后,输入框自动获取了焦点