Vue 组件通信

作者: 北辰_狼月 | 来源:发表于2019-04-07 22:57 被阅读100次

    组件作为Vue中的核心概念,是值得我们深入研究的课题之一,通过研究它,我们可以理解更高深的思想,可以提升自己的开发技巧。而今天,我要讨论的是Vue的组件通信。
    众所周知,组件通信是通过props和emit去完成的,但实际上,这只是众多方式中的一种而已。而且针对不同的情况,会有更合适的方法。下面就听我慢慢道来。

    1.props和emit

    父组件:
    <template>
    <div class="parent-box">
          <h3>我是父元素,props方式</h3>
          <p class="content">
            通过$emit获得子元素属性{{children2}}
          </p>
          <Children2 @changeChild2="changeChild2"></Children2>
    </div>
    </template>
    
    <script>
    data(){
        return {
          children2:'children2',
        }
      },
      methods:{
        changeChild2(val){
          this.children2 = val
        },
    }
    </script>
    
    子组件
    <template>
        <div class="children-box">
            <h4>我是子元素</h4>
            <button @click="clickEvent">改值</button>
            <p>通过props通信 {{value}}</p>
        </div>
    </template>
    <script>
        export default {
            props:['value'],
            methods:{
                clickEvent(){
                    // 核心代码
                    this.$emit('changeChild2',Math.random())
                }
            }
        }
    </script>
    

    大体效果如下:


    20190407_211309.gif

    可以看到,通过点击按钮,可以改变通过props传入子组件的value属性。因为这种方式是大家最常用的一种方式,这里就不做详细解释了。

    2.$parent$children

    $parent 属性可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。
    $children可以访问当前实例的直接子组件。
    下面来看一个例子,代码如下,注意注释部分。

    父组件
    <template>
    <div class="parent-box">
          <h3>我是父元素,$parent,$children方式</h3>
          <p class="content">
            通过$children获得子元素属性{{children1}}
          </p>
          <Children1></Children1>
    </div>
    </template>
    <script>
    data(){
        return {
          parent1:'parent1',
          children1:'children1',
        }
      },
     mounted() {
        // 核心代码,通过$children获取子组件的属性
        this.children1 = this.$children[0]._data.value
      },
    </script>
    子组件
    <template>
        <div class="children-box">
            <h4>我是子元素</h4>
            <input type="text" v-model="parent">
            <p>通过$parent获取父元素的属性 {{parent}}</p>
        </div>
    </template>
    <script>
        export default {
            data(){
                return {
                    // 核心代码,通过$parent获得父组件的属性
                    parent:this.$parent._data.parent1
                }
            },
            mounted() {
            },
            watch:{
                parent(val){
                    // 核心代码,改变父组件中的属性
                    this.$parent._data.parent1 = val
                }
            }
        }
    </script>
    

    大体效果如下:


    20190407_212944.gif

    可以看到,通过this.$parent._data.parent1 = val,改变子组件中parent的值,然后赋值给父组件的parent1可以直接改变父组件的属性值。
    虽然这种方式比较方便快捷,但有很大的副作用,就如官网所说:

    在绝大多数情况下,触达父级组件会使得你的应用更难调试和理解,尤其是当你变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。

    节制地使用 $parent$children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信

    3.总线方式

    有时候,我们的组件并不止父子关系这么简单,可能兄弟组件之间也要进行通信,而EventBus就能解决这个问题,相对于vuex它更轻量,不需要我们引入vuex这个庞然大物,更加适合小型项目。
    我们在主实例App之外,单独定义一个空的Bus实例,来进行组件间的通信。
    下面来看一个例子,代码如下

    bus.js
    // 核心代码
    import Vue from 'vue'
    var Bus = new Vue()
    export default Bus
    
    父组件:
    <template>
    <div class="parent-box">
          <h3>我是父元素,总线方式</h3>
          <Children31></Children31>
          <Children32></Children32>
    </div>
    </template>
    
    子组件1
    <template>
        <div class="children-box">
            <h4>我是子元素</h4>
            <p>通过总线方式通信 {{msg}}</p>
        </div>
    </template>
    <script>
        import Bus from '../bus'
        export default {
            data(){
                return {
                    msg:'hello world'
                }
            },
            mounted() {
            },
            created(){
                  // 核心代码,接受事件
                Bus.$on('setMsg',val=>{
                    this.msg = val
                })
            },
        }
    </script>
    子组件2
    <template>
        <div class="children-box">
            <h4>我是子元素</h4>
            <input type="text" v-model="msg">
            <p>通过总线方式通信 {{msg}}</p>
    
        </div>
    </template>
    <script>
        import Bus from '../bus'
        export default {
            data(){
                return {
                    msg:'hello world'
                }
            },
            mounted() {
            },
            watch:{
                msg:function (newVal) {
                    // 核心代码发出事件
                    Bus.$emit('setMsg',newVal)
                },
            }
        }
    </script>
    

    大体效果如下:


    20190407_215821.gif

    可以看到,我改变子组件2 input的值会触发emit事件,去改变子组件1中的msg。

    4.$attrs$listeners

    $attrs$listeners是2.4.0才新加入的方法,用来解决组件的跨级传输非常有用。试想有A、B、C三个组件,A包含B,B包含C,如果我想在A上给C传参,并且接收C的事件怎么办呢?
    原先,我们只使用pros去传参的话,就只能拿B作为中转组件,B组件定义足够多的pros,不仅仅用于自身,还要用于传输给C,而事件的传递,也只能一层层地往上传,这样就会使代码很繁琐,臃肿,不利于维护。
    $attrs$listeners就是用来处理这种情况的,代码如下

    父组件A
    <template>
    <div class="parent-box">
          <h3>我是父元素A,$attrs,$listeners方式</h3>
          <Children4 :value1="value1" :value2="value2" @clickEvent1="clickEvent1" @clickEvent2="clickEvent2"></Children4>
    </div>
    </template>
    <script>
    data(){
          value1:'B',
          value2:'C',
    },
    methods:{
    clickEvent1(){
          this.value1 = Math.random()
        },
        clickEvent2(){
          this.value2 = Math.random()
        }
    }
    </script>
    子组件B
    <template>
        <div class="children-box">
            <h4>我是子元素B</h4>
            <p>{{value1}}</p>
            <button @click="clickEvent">改变value1的值</button>
            <Children42 v-bind="$attrs" v-on="$listeners"></Children42>
        </div>
    </template>
    <script>
        import Children42 from './Children4.2'
        export default {
            name:'Children41',
            inheritAttrs:false,
            props:['value1'],
            components:{
                Children42
            },
            methods:{
                clickEvent(){
                    this.$emit('clickEvent1')
                }
            },
        }
    </script>
    子组件C
    <template>
        <div class="children-box">
            <h4>我是子元素C</h4>
            <p>{{value2}}</p>
            <button @click="clickEvent">改变value1的值</button>
        </div>
    </template>
    <script>
        export default {
            name:'Children42',
            inheritAttrs:false,
            props:['value2'],
            data(){
                return {
                }
            },
            mounted() {
            },
            methods:{
                clickEvent(){
                    this.$emit('clickEvent2')
                }
            },
        }
    </script>
    

    大体效果如下:


    20190407_221354.gif

    可以看到,我们只要在引用C组件的时候,加入v-bind="$attrs" v-on="$listeners"两个属性即可,这样,C组件就可以接收到来自A组件的值,A组件也能接收到来自C组件的事件。
    如此以来,就不需要在B组件定义中转的属性和方法,如果你的组件结构比较复杂,这种方式可以很大程度减少代码的冗余,更加的轻量化。

    5.provide和inject

    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
    这相对于attrs和listeners可能更加简介,只需要父组件提供变量,子组件注入就行,不需要在中间组件写什么代码,但并不推荐在业务代码中使用,正如官方所说。

    provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

    因为provide inject 会有一个类似冒泡的特性,数据源有可能在中间被”“打断”,甚至是有可能被组件库中的组件打断,或者打断组件库中的provide,不利于维护

    代码如下:

    父组件
    <template>
    <div class="parent-box">
          <h3>我是父元素,provide,inject方式</h3>
          <Children5></Children5>
        </div>
    </template>
    <script>
    data(){
    return {
         theme:'blue'
    },
    // 核心代码
    provide(){
        return {
          test:this
        }
    },
    }
    </script>
    
    子组件
    <template>
        <div class="children-box">
            <h4 :style="{color:value.theme}">我是子元素</h4>
            <div @click="changeValue">改颜色</div>
        </div>
    </template>
    <script>
        export default {
            // 核心代码
            inject: {
                value:{
                    from:'test',
                    default:()=>{}
                }
            },
            methods:{
                changeValue(){
                    this.value.theme = 'red'
                }
            }
        }
    </script>
    

    大体效果如下:


    20190407_223227.gif

    我们在父组件中提供一个test属性,然后赋值为this,这里之所以赋值this,是为了让provide和inject的绑定变成可响应的,这样,我再子组件中就可以直接改变父组件的theme属性。

    6. Vuex

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    Vuex的功能强大,但应对简单的组件通信用Vuex就显得多余了,有种杀鸡用牛刀的感觉,还会增加我们代码的理解难度。
    Vuex作为我们必须掌握的技能之一,这里也不再赘述,不了解的话,官网就是最好的学习材料。

    总结

    vue中组件通信的方式很多,应对不同情况,灵活地采用最适合的方式,才能使我们的代码变得优雅。

    以上是目前为止,我所知的所有通信方式,如有遗漏,欢迎补充。

    以下,是代码的demo地址
    https://github.com/hanwolfxue/blog-demo-vue-communicate.git

    相关文章

      网友评论

        本文标题:Vue 组件通信

        本文链接:https://www.haomeiwen.com/subject/gmkeiqtx.html