Vue10-全局事件总线+消息订阅和发布

Vue10-全局事件总线+消息订阅和发布

1.全局事件总线

  1. 全局事件总线是一种组件之间通信的方式,可以实现任意组件之间的通信。
  2. 安装全局事件总线。
new Vue({
    ...
    beforeCreate() {
    	Vue.prototype.$bus = this; // 安装全局事件总线,将$bus指定为vm。
	}
})
  1. 使用全局事件总线。
// 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', '数据');
  1. 销毁全局事件。在$bus中绑定的事件会被所有VueComponent实例所共享,所有最好在接受数据的组件的beforeDestroy()中,使用$off()解绑当前组件所绑定的事件。

2.全局事件总线-Student和School组件直接的通信

  1. 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; // 安装全局事件总线
    }
})
  1. 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>
  1. 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案例添加全局事件总线

  1. 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;
    }
})
  1. 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>
  1. 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.消息订阅和发布

  1. 消息订阅和发布是一种组件间通信的方式,适用于任何组件间的通信。

  2. 原生JavaScript不能支持消息订阅和发布,所有需要引入pubsub-js来支持消息订阅和发布,pubsub-js可以支持任何前端框架的消息订阅和发布。

  3. 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组件之间的通信

  1. 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>
  1. 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案例添加消息订阅和发布

  1. 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>
  1. 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组件添加编辑按钮

  1. this.$nextTick(函数)中的函数会在下一次DOM更新完成后执行。
  2. $nextTick的使用时间。当数据更新完成后,要基于更新后的新DOM进行的某些操作,就需要在$nextTick所指定的回调函数中执行。
  3. 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>
posted @ 2022-11-25 11:07  行稳致远方  阅读(179)  评论(0)    收藏  举报