深入浅出学习组件(二):组件通信知识梳理


上一部分我们已经学习了通过props从父组件向子组件传递数据,但Vue组件通信的场景有很多种:

很多初学者可能有和我一样的问题,根本分不清什么是父组件,什么是子组件,这让后面的学习非常费劲,所以我们来讲一下怎样分别两者。

一、什么是子组件?什么是父组件?

我们先看一段代码:

<div id="app">
    <father-component message="来自父组件的数据"></father-component>
    <father-component message="现在显示的是father-component父组件的子组件"></father-component>
    <!--数据message就是通过props从父级传递过来的,在组件的自定义标签上直接写该props的名称-->
</div>
<script>
    Vue.component('father-component',{
        props:['message'],
        template:'<div>{{message}}</div>'
    });
    var app = new Vue({
        el:'#app'
    })
</script>

这段代码中全局注册、被命名为father-component的组件就是父组件,如果父组件在页面中被使用,那么被使用的自定义标签就是子组件。上例也算是温习了上节课的内容,props可以通过父级组建传递数据到子组件。

分清了什么是子组件和父组件,我们来讲一下v-on指令和自定义事件。

二、自定义事件

当子组件需要向父组件传递数据的时候,需要用到自定义事件。而v-on指令除了监听DOM事件之外,还可以用于组件之间的自定义事件。

Vue组件有类似于JavaScript中观察者模式的设计模式,可以触发事件和监听事件。在Vue中,子组件触发事件用到的是$ emit() ,父组件监听子组件的事件用到的是$ on()。父组件也可以直接在子组件的自定义标签上使用v-on监听子组件触发的自定义事件。

我们用购物车通过按钮操作商品数量的例子来说明:

首先我们来分析一下需求

  • 我们需要得到的格式是:商品名称 + 数字 -
  • 其中+是按钮,每次按+数字加;- 是按钮,每次按 - 数字减1
  • 父组件能够获得子组件中数字的具体值

其次我们来分析一下逻辑

1、用户点击子组件按钮,触发子组件的按钮点击事件

  • 更新逻辑(this.counter++)
  • 分发事件-数据,把分发的内容保留在子组件的对象实例

2、父组件触发了被子组件分发的事件

  • 处理方法中的参数,获得子组件的分发事件的数据
  • 父组件获得子组件数据后的具体处理过程
<div id="app">
        <custom-cart @add="handleAdd" @reduce="handleReduce" product="商品"></custom-cart>
        购物车商品数量:{{total}}
</div>
<script>
    Vue.component('custom-cart',{
        props:['product'],
        data:function(){
            return {
                quantity:1
            }
        },
        template:'<div>{{product}}<button @click="reduce">-</button>{{quantity}}' +
            '<button @click="add">+</button></div>',
        methods:{
            add:function(){
                this.quantity++;
                this.$emit('add',this.quantity);
            },
            reduce:function(){
                this.quantity--;
                this.$emit('reduce',this.quantity);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            total:1
        },
        methods:{
            handleAdd:function (quantity) {
                this.total = quantity;
            },
            handleReduce:function(quantity){
                this.total = quantity;
           }
        }
    })
</script>

上面示例中,子组件有两个按钮,分别实现加1和减1的效果,在改变组件的data“total”后,通过$emit()再把它传递给父组件,父组件用v-on:add和v-on:reduce(示例中使用的是语法糖)。

$emit() 方法的第一个参数是自定义事件的名称,第二个参数是要传递的数据,可以选择填多个或者不填。这两个参数组成一个键值对,在子组件向父组件分发数据时,直接调用自定义事件名称下的方法就可以了。子组件触发add和reduce,然后调用handleAdd和handleReduce方法,才可以加1减1。

三、v-model在组件中的使用

在之前我们就讲过,v-model是用来传递双向数据流的指令,在组件中,v-model可以进行数据的双向绑定。但是实现双向绑定的v-model组件要满足两个条件:

  • 接收一个value属性
  • 在有新的value时触发input事件
<div id="app">
    <custom-input v-model="inputVal"></custom-input>
    {{inputVal}}
</div>
<script>
    Vue.component('custom-input',{
        model:{
            props:'value',
            event:'input'
        },
        props:['value'],
        template:'<input :value="value" @input="update"/>',
        methods:{
            update:function (event) {
                this.$emit('input',event.target.value);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            inputVal:'test'
        }
    })
</script>

上述例子中,我们在输入框中修改value值,就可以看到输入框外的值改变。

在这段代码中,子组件向父组件分发数据和父组件向子组件传递数据这两个过程中,没有对赋值的数据做逻辑描述,因为v-model将它们进行了双向绑定,只是在update方法中,我们将’input’值赋成已改变的值,等待父组件的调用。

我们来明确一下v-model的逻辑
1) 当父组件向子组件传值时,通过v-model传给 子组件 model里定的 props对应的属性;

2) 当子组件向父组件传值是,通过v-model传给 子组件 model里定的event对应的事件名称input,在$emit分发的事件中找到对应内容,相当于(父组件)触发对应的分发事件

不仅是input可以实现双向绑定,select的自定义事件也可以进行双向绑定:

<div id="app">
    <cst-select v-model="selectValue"></cst-select>
    {{selectValue}}
</div>
<script>
    Vue.component('cst-select', {
        model:{
            prop:'selected',  /*父组件给子组件传值的属性*/
            event:'change'   /*子组件给父组件传值时使用的分发事件名称*/
        },
        props: ['selected'],
        template: '<select :value="selected" @change="update">' +
            '<option value="1">天津</option>' +
            '<option value="2">北京</option>' +
            ' </select>'
        , methods:{
            update:function (event) {
                this.$emit('change',event.target.value);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            selectValue:1
    }
    })
</script>

而且checkBox的自定义事件也可以进行双向绑定:

<div id="app">
    <custom-checked v-model="checkedValue"></custom-checked>
    {{checkedValue}}
</div>
<script>
   Vue.component('custom-checked', {
    model:{
        props:'value',
        event:'change'
    },
    props: ['value'],
    template: '<input type="checkbox" :checked="value" @change="update">' +
    '</input>',
    methods:{
        update:function (event) {
            this.$emit('change',event.target.checked );
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            checkedValue:true
    }
    })
</script>

四、非父子组件通信

在实际业务中,除了父子组件通信外,还有许多非父子组件通信的场景,非父子组件一般有两种,兄弟组件和跨多级组件。

我们推荐使用一个空的Vue实例作为中央时间总线(bus),也就是一个中介,就像你和出租者之间的房屋中介一样,用下面的代码进行举例:

<div id="app">
    {{message}}
    <component-a></component-a>
</div>
<script>
    var bus = new Vue();
    Vue.component('component-a',{
        template:'<button @click="handleEvent">传递事件</button>',
        methods:{
            handleEvent:function () {
                bus.$emit('on-message','来自组件component-a的内容');
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        },
        mounted:function () {
            var _this = this;
            //在实例初始化时,监听来自bus实例的事件
            bus.$on('on-message',function (msg) {
                _this.message = msg;
            });
        }
    })
</script>

首先创建一个名为bus的空Vue实例,里面没有任何内容,全局定义一个组件component-a,最后创建Vue实例app,在app初始化时,也就是生命周期mounted钩子函数里监听了来自bus的事件on-message,而在组件component-a中,点击按钮会通过bus把事件on-message发出去。

此时app就会接收来自bus的事件,进而在回调中完成自己的业务逻辑。此时component-a和mounted钩子函数不是父子组件,但是它们通过bus中介完成了业务逻辑,可以正常使用$ on()和$ emit()。

下一部分我们会讲组件的插槽以及组件部分的综合案例,敬请期待!

发布了18 篇原创文章 · 获赞 16 · 访问量 1458

猜你喜欢

转载自blog.csdn.net/abc701110/article/details/105029095
今日推荐