vue纵横研究院VU...

Vue 组件通信

2019-04-07  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读