Vue 组件通信

组件作为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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容