Vue.js入门 0x6 组件(Component)(3)组件通信

    从父组件向子组件通信,通过 props 传递数据就可以了,但 Vue 组件通信的场景不止有这一种,归纳起来,组件之间通信可以用下图表示。

    组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。

Vue.js入门 0x6 组件(Component)(3)组件通信

 自定义事件

    当子组件需要向父组件传递数据时,就要用到自定义事件。v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。Vue组件有一套模式,子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。
    父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。

<div id="app">
    <p>总数:{{total}}</p>
    <my-component 
        @increase="handleGetTotal"
        @reduce="handleGetTotal"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component('my-component',{
        template:'\
        <div>\
            <button @click="handleIncrease">+1</button>\
            <button @click="handleReduce">-1</button>\
        </div>',
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            handleIncrease:function(){
                this.counter++;
                this.$emit('increase',this.counter);
            },
            handleReduce:function(){
                this.counter--;
                this.$emit('reduce',this.counter);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleGetTotal:function(total){
                this.total = total;
            }
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

     上面示例中,子组件有两个按钮,分别实现加1和减l的效果,在改变组件的data“counter"后,通过$emit()再把它传递给父组件,父组件用v-on:increase和v-on:reduce(示例使用的是语法糖)。$emit()方法的第一个参数是自定义事件的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或填写多个。

使用 v-model 

<div id="app">
    <p>总数:{{total}}</p>
    <my-component v-model="total"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component('my-component',{
        template:'<button @click="handleClick">+1</button>',
        data:function(){
            return {
                counter:0
            }
        },
        methods:{
            handleClick:function(){
                this.counter++;
                this.$emit('input',this.counter);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

     仍然是点击按钮加 1 的效果 , 不过这次组件$emit()的事件名是特殊的 input, 在使用组件的父级,井没有在<my-component>上使用@input= "handler",而是直接用了 v-model 绑定的一个数据total 。这也可以称作是一个语法糖,因为上面的示例可以间接地用自定义事件来实现。

    v-model还可以用来创建自定义的表单输入组件 ,进行数据双向绑定

    实现这样一个具有双向绑定的 v-model 组件要满足下面两个要求 :
    •接收一个 value 属性。
    • 在有新的 value 时触发 input 事件。

<div id="app">
    <p>总数:{{total}}</p>
    <my-component v-model="total"></my-component>
    <button @click="handleReduce">-1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component('my-component',{
        props:['value'],
        template:'<input :value="value" @input="updateValue">',
        methods:{
            updateValue:function(event){
                this.$emit('input',event.target.value);
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleReduce:function(){
                this.total--;
            }
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

 非父子组件通信

    在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种,兄弟组件和跨多级组件。在Vue.2.x中,推荐使用一个空的Vue实例作为*事件总线(bus),也就是一个中介。

<div id="app">
    {{message}}
    <component-a></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    //定义中介bus
    var bus = new Vue();
    //定义全局组件component-a
    Vue.component('component-a',{
        template:'<button @click="handleEvent">传递事件</button>',
        methods:{
            //点击按钮时通过bus把事件on-message发出去
            handleEvent:function(){
                bus.$emit('on-message','来自组件component-a的内容');
            }
        }
    });
    //app收到来自bus的事件,进而在回调里完成自己的业务逻辑
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        },
        //mounted钩子函数
        mounted:function(){
            var _this = this;
            //在实例化时,监听来自bus实例的事件
            bus.$on('on-message',function(msg){
                _this.message = msg;
            });
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

     父链

    在子组件中,使用 this.$parent 可以直接访问该组件的父实例或组件,父组件也可以通过this.$children 访问它所有的子组件,而且可以递归向上或向下无线访问, 直到根实例或最内层的组件。

<div id="app">
    {{message}}
    <component-a></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component('component-a',{
        template:'<button @click="handleEvent">通过父链直接修改数据</button>',
        methods:{
            handleEvent:function(){
                //访问到父链后,可以做任何操作,比如直接修改数据
                this.$parent.message = '来自组件component-a的内容'
            }
        }
    });
    //app收到来自bus的事件,进而在回调里完成自己的业务逻辑
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

     尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧藕合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。父子组件最好还是通过props和$emit来通信。

      子组件索引

    当子组件较多时 , 通过 this.$children 来一一遍历出我们需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。 Vue 提供了子组件索引的方法,用特殊的属性 ref来为子组件指定一个索引名称。

    在父组件模板中,子组件标签上使用 ref 指定一个名称,井在父组件内通过 this.$refs 来访问指定名称的子组件 。

<div id="app">
    <button @click="handleRef">通过ref获取子组件实例</button>
    <component-a ref="comA"></component-a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
    Vue.component('component-a',{
        template:'<div>子组件</div>',
        data:function(){
            return {
                message:'子组件的内容'
            }
        }
    });
    
    var app = new Vue({
        el:'#app',
        methods:{
            handleRef:function(){
                //通过¥refs来访问指定的实例
                var msg = this.$refs.comA.message;
                console.log(msg);
            }
        }
    })
</script>

Vue.js入门 0x6 组件(Component)(3)组件通信

    $refs只在组件渲染完成后才填充,并且它是非响应式的,它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs。