• Vue2(十):全局事件总线、消息订阅与发布、TodoList的编辑功能、$nextTick、动画


    一、全局事件总线

    一种组件间通信的方式,适用于任意组件间通信。通俗理解就是一个定义在所有组件之外的公共嘎达,这个嘎达可以有vm或vc上的同款$on、$off、$emit,也可以让所有组件都访问到。要想实现这个事情,只能在Vue.prototype上添加一个属性,值是vm或vc
    在这里插入图片描述

    1.安装全局事件总线

    安装的话用vc也行用vm也行,用vc的话可以这么写:

    const Demo = Vue.extend({});
    const d = new Demo();
    Vue.prototype.$bus = d;
    
    • 1
    • 2
    • 3

    但是我们一般都用vm:

    new Vue({
    	......
    	 //放这个函数里,是因为模板还未解析,这个函数在自定义事件定义之前,是不会报错滴
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,起个名叫$bus,把当前vm给特
    	},
        ......
    }) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.使用事件总线

    接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

    methods(){
      demo(data){......}
    }
    ......
    mounted() {
      this.$bus.$on('xxxx',this.demo)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.提供数据

    任意一个组件,都可以给上面说的A组件传数据

      methods:{
        sendStudentName(){
            this.$bus.$emit('xxxx',this.name)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.组件销毁前最好解绑

    最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
    因为接收数据的组件A中定义的回调函数和自定义事件是绑定的,而这个用来接收数据的组件实例A都销毁了,回调函数也没了,那这个xxxx自定义事件也就没用了,你留着会污染全局环境(这块儿有点迷糊)

    beforeDestory(){
        this.$bus.$off('xxxx')
    }
    
    • 1
    • 2
    • 3

    5.TodoList中的孙传父

    之前我们孙传父都是父亲传给儿子函数,儿子传给孙子函数,然后孙子再调用函数传值,很麻烦,但是现在我们可以用全局事件总线实现孙子给父亲传数据
    (1)首先安装全局事件总线

    new Vue({
        el: '#app',
        render: h => h(App),
        beforeCreate() {
            Vue.prototype.$bus = this; //创建全局事件总线
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)在App中绑定全局自定义事件,并使用之前写好的回调

    mounted() {
        //挂载完成后给全局事件总线添加事件
        this.$bus.$on('changeTodo', this.changeTodo);
        this.$bus.$on('deleteTodo', this.deleteTodo);
    },
    beforeDestroy() {
       //最好在销毁前解绑
        this.$bus.$off(['changeTodo', 'deleteTodo']);
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3)Item中触发事件,并把数据id传过去

    <input type="checkbox" :checked="todo.done" @click="handleChange(todo.id)" />
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除button>
    
    • 1
    • 2
    handleChange(id) {
        //触发全局事件总线中的事件
        this.$bus.$emit('changeTodo', id);
    },
    handleDelete(id) {
        if (confirm('确定要删除吗?'))  //点确定是true,取消是false
            this.$bus.$emit('deleteTodo', id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、消息订阅与发布(pubsub)

    1.使用方法

    这玩意儿用的不多,和全局事件总线写法差不多,但是全局事件总线更好,因为是在Vue身上操作,但是这个的话要引入第三方库,库有很多,比如pubsub-js

    安装pubsub:npm i pubsub-js
    引入:import pubsub from 'pubsub-js

    接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
    接收两个参数,第一个是消息名字,第二个是传过来的数据

    methods(){
      demo(msgName,data){......}
    }
    ......
    mounted() {
      this.pubsubId = pubsub.subscribe('xxx',this.demo) //订阅消息
    },   
    beforeDestroy() {
       pubsub.unsubscribe(this.pubsubId); //销毁时取消订阅
    },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    提供数据:

    methods: {
        sendStudentName() {
            // this.$bus.$emit('hello', this.name);
            pubsub.publish('hello', this.name); //发布消息并传数据
        }
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.TodoList中小用一下

    changeTodo用全局事件总线,deleteTodo用消息订阅与发布,对比着看看
    App:

    mounted() {
        //挂载完成后给全局事件总线添加事件
        this.$bus.$on('changeTodo', this.changeTodo);
        
        //deleteTodo用订阅消息写一下子
        this.pubsubId = pubsub.subscribe('deleteTodo', this.deleteTodo);
    },
    beforeDestroy() {
        //最好在销毁前解绑和取消订阅
        this.$bus.$off('changeTodo');
        pubsub.unsubscribe(this.pubsubId);
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里别忘了这个subscribe里的回调,接收两个参数,第一个是消息名,后面才是数据,所以deleTodo方法得加个参数

    deleteTodo(msgName, id) {
    			this.todos = this.todos.filter(todo => todo.id !== id);
            },
    
    • 1
    • 2
    • 3

    Item:

    handleChange(id) {
        //触发全局事件总线中的事件
        this.$bus.$emit('changeTodo', id);
    },
    handleDelete(id) {
        if (confirm('确定要删除吗?'))  //点确定是true,取消是false
            pubsub.publish('deleteTodo', id);  //发布消息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、TodoList的编辑功能

    这功能用到的细节比较多,给我整懵了

    1.思路

    首先得有一个按钮,点击之后出现input框,框里呢是todo.title,而且原来的span要隐藏。然后修改完之后,失去焦点会自动更新数据,并且span出现,input框隐藏。
    除此之外还有个细节,那就是点击编辑要自动获取焦点,要不然会有bug(点击编辑,然后突然不想改了,还得手动点一下input,再点下别的地方,才会变回span)

    想实现spaninput的来回切换,就要给todo添加新的属性,用来标识这个变换,这里起名叫isEdit

    所以大致思路:给标签添加事件 => 点击编辑按钮切换span为input => 失去焦点传数据 => App收数据 => 解决焦点bug

    2.给标签添加事件

    (1)isEdit一上来是没有的,所以todo.isEdit = false,再加上默认上来显示的是span,所以span加个v-show="!todo.isEdit",input加个v-show="todo.isEdit" ,button加v-show="!todo.isEdit"是因为我们一般编辑时,这个按钮应该消失才对,所以和span一致

    (2)由于props接过来的数据不能改,所以使用单向数据绑定:value="todo.title" (不过这里很奇怪,明明isEdit也是给todo添加了属性,奇怪奇怪,画个问号?????

    (3)ref="inputTitle" 是为了方便后面拿到input元素然后操作它写的(nextTick)

    (4)@blur="handleBlur(todo, $event)"是失去焦点时触发的事件,用来给App传值

    <span v-show="!todo.isEdit">{{ todo.title }}span>
    <input 
    type="text" 
    v-show="todo.isEdit" 
    :value="todo.title" 
    ref="inputTitle" 
    @blur="handleBlur(todo, $event)">
     <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑button>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.点击编辑按钮切换span为input

    点击编辑给todo追加属性,用来切换span为input。这里考虑到给todo追加属性的问题,如果想要让Vue监测到这个属性,那么必须使用$set来添加isEdit,且默认值为true(因为编辑的时候显示的时input啊,想想v-show="todo.isEdit")。

    但是这里边有点儿问题,如果已经添加过了isEdit,那每次点击编辑按钮,都会添加一次isEdit属性,这样是不太好的,所以要加个判断,添加过了就改成true,没添加过就添加个true

       handleEdit(todo) {
            if (todo.isEdit !== undefined) {
                console.log('todo里有isEdit属性了')
                todo.isEdit = true;
            } else {
                console.log('todo里没有isEdit属性')
                this.$set(todo, 'isEdit', true);
            }
            this.$nextTick(function () {
                this.$refs.inputTitle.focus();
            })
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.失去焦点传数据

    失去焦点首先input得变回span,然后使用全局事件总线传值,传值一定要传当前input框的value值,因为这才是你修改后的值,使用事件对象获取。(当然,别忘了id也要传)

    handleBlur(todo, e) {
        todo.isEdit = false;
        if (!e.target.value.trim()) return alert('值不能为空!');  //trim去掉空格
        this.$bus.$emit('editTodo', todo.id, e.target.value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.App收数据

    把input框里你写的东西拿过来,给对应的todo.title

    //6.实现编辑todo
    methods: {
    		......
            editTodo(id, newVal) {
                this.todos.forEach((todo) => {
                    if (todo.id === id) { todo.title = newVal }
                });
            }
       }
    mounted() {
    	......
        //实现编辑功能,接收数据
        this.$bus.$on('editTodo', this.editTodo);
    },
    beforeDestroy() {
        //最好在销毁前解绑
        this.$bus.$off('editTodo');
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    四、$nextTick

    1、语法:this.$nextTick(回调函数)
    2、作用:在下一次 DOM 更新结束,v-for循环结束后执行其指定的回调。
    3、什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时(如input自动获取焦点),要在nextTick所指定的回调函数中执行。

    4、比如刚才点击编辑后,input框没法自动获取焦点,那么我就得先点一下,再点别处儿才能切换回去,如果直接this.$refs.inputTitle.focus();不行,因为这个函数虽然动了isEdit的值,但是模板重新解析也得等这个函数走完啊,那input还没创建出来呢,就focus了,肯定是不行滴。
    有个办法就是用异步,也可以解决,但是更好的办法是$nextTick

    handleEdit(todo) {
        if (todo.isEdit !== undefined) {
            console.log('todo里有isEdit属性了')
            todo.isEdit = true;
    
        } else {
            console.log('todo里没有isEdit属性')
            this.$set(todo, 'isEdit', true);
        }
        //自动获取焦点,这么写不行,因为input还没放入页面呢
        // this.$refs.inputTitle.focus(); //这句话走完,才会解析模板(isEdit改变)
        //开个异步,就可以解决问题
        // setTimeout(() => {
        //     this.$refs.inputTitle.focus();
        // }, 200);
        //但是,官方儿考虑到了这个问题,于是给了个api,$nextTick在模板解析完后,才调用回调函数
        this.$nextTick(function () {
            this.$refs.inputTitle.focus();
        })
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    五、动画

    1、作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

    2、写法:

    准备好样式:

    元素进入的样式:
    v-enter:进入的起点
    v-enter-active:进入过程中
    v-enter-to:进入的终点

    元素离开的样式:
    v-leave:离开的起点
    v-leave-active:离开过程中
    v-leave-to:离开的终点

    使用包裹要过度的元素,并配置name属性:

    <transition name="hello">
    	<h1 v-show="isShow">你好啊!h1>
    transition>
    
    • 1
    • 2
    • 3

    3、备注:若有多个元素需要过度,则需要使用:,且每个元素都要指定key值。

  • 相关阅读:
    深度学习 | Pytorch深度学习实践 (Chapter 1~9)
    《联邦学习实战—杨强》之使用Python从零开始实现一个简单的横向联邦学习模型
    美国fba海运清关基本流程
    5‘-二磷酸鸟嘌呤核苷-岩藻糖二钠盐,GDP-Fucose,15839-70-0
    详细介绍区块链之挖矿
    找合适的PMP机构只需2步搞定,一查二问
    File的常见成员方法
    typescript对类型的管理和查找规则
    UGUI学习笔记(十二)自制血条控件
    Java模拟双向链表,增删操作
  • 原文地址:https://blog.csdn.net/weixin_42044763/article/details/126513231